bloodhound-1.0.0.0: tests/Test/NodesSpec.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Test.NodesSpec (spec) where
import Data.Aeson (Value (..), decode, encode)
import Data.Aeson.Key (toText)
import Data.Aeson.KeyMap qualified as KM
import Data.Aeson.Types (parseMaybe, withObject, (.:))
import Data.ByteString.Lazy.Char8 qualified as BL
import Data.List.NonEmpty (NonEmpty ((:|)))
import Data.Text qualified as T
import Test.Hspec (Spec, describe, it, shouldBe)
import TestsUtils.Common
import TestsUtils.Import
import Prelude
spec :: Spec
spec = do
describe "getNodesInfo" $
it "fetches the responding node when LocalNode is used" $
withTestEnv $ do
NodesInfo {..} <- performBHRequest $ getNodesInfo LocalNode
-- This is really just a smoke test for response
-- parsing. Node info is so variable, there's not much I can
-- assert here.
liftIO $ length nodesInfo `shouldBe` 1
describe "getNodesStats" $
it "fetches the responding node when LocalNode is used" $
withTestEnv $ do
NodesStats {..} <- performBHRequest $ getNodesStats LocalNode
liftIO $ length nodesStats `shouldBe` 1
describe "getNodesUsage" $
it "fetches the responding node when LocalNode is used" $
withTestEnv $ do
NodesUsage {..} <- performBHRequest $ getNodesUsage LocalNode
liftIO $ length nodesUsage `shouldBe` 1
describe "getNodesHotThreads" $ do
-- The hot_threads endpoint returns plain text rather than JSON, so
-- this is a smoke test for the UTF-8 body decoder in
-- 'Internal.Utils.Requests.getText'.
it "returns a non-empty plain-text report with no selector" $
withTestEnv $ do
report <- performBHRequest $ getNodesHotThreads Nothing Nothing
liftIO $ (not $ T.null report) `shouldBe` True
it "honours the threads query parameter (ThreadCount 1)" $
withTestEnv $ do
report <- performBHRequest $ getNodesHotThreads Nothing (Just (ThreadCount 1))
liftIO $ (not $ T.null report) `shouldBe` True
it "accepts a specific node selector" $
withTestEnv $ do
NodesInfo {..} <- performBHRequest $ getNodesInfo LocalNode
let targetNodeName = nodeInfoName $ head nodesInfo
report <- performBHRequest $ getNodesHotThreads (Just (NodeByName targetNodeName)) Nothing
liftIO $ (not $ T.null report) `shouldBe` True
-- Backend-agnostic live smoke test for the new URI parameters.
-- 'snapshots' and 'ignore_idle_threads' are accepted by both ES and
-- OpenSearch, so they are safe to exercise against whatever backend
-- is up. (The 'type' parameter is also universal, but is covered by
-- the pure endpoint-shape tests below.)
it "getNodesHotThreadsWith honours snapshots and ignore_idle_threads" $
withTestEnv $ do
let opts = defaultHotThreadsOptions {htoSnapshots = Just 1, htoIgnoreIdleThreads = Just False}
report <- performBHRequest $ getNodesHotThreadsWith Nothing opts
liftIO $ (not $ T.null report) `shouldBe` True
-- The hot-threads kind is named @type@ on every backend (the
-- historical @doc_type@ is rejected with HTTP 400). This live test
-- pins that the emitted @type=cpu@ request is accepted by the server.
it "getNodesHotThreadsWith honours type (the universally accepted kind param)" $
withTestEnv $ do
let opts = defaultHotThreadsOptions {htoDocType = Just HotThreadsDocTypeCPU}
report <- performBHRequest $ getNodesHotThreadsWith Nothing opts
liftIO $ (not $ T.null report) `shouldBe` True
-- Endpoint-shape tests for the fully-parameterised variant. Pure (no
-- live ES) — they pin the URL path and query string so the wire format
-- cannot drift silently. Mirrors the precedent set by
-- @getNodesInfoWith endpoint shape@ above.
describe "getNodesHotThreadsWith endpoint shape" $ do
let path req = getRawEndpoint (bhRequestEndpoint req)
queries req = getRawEndpointQueries (bhRequestEndpoint req)
it "default options -> identical to legacy getNodesHotThreads Nothing Nothing" $
path (getNodesHotThreadsWith Nothing defaultHotThreadsOptions)
`shouldBe` path (getNodesHotThreads Nothing Nothing)
it "default options -> no query string" $
queries (getNodesHotThreadsWith Nothing defaultHotThreadsOptions) `shouldBe` []
it "threads preserved via legacy delegation (Just 1)" $
path (getNodesHotThreads Nothing (Just (ThreadCount 1)))
`shouldBe` path (getNodesHotThreadsWith Nothing defaultHotThreadsOptions {htoThreads = Just (ThreadCount 1)})
it "Just selector renders /_nodes/{seg}/hot_threads" $
path (getNodesHotThreadsWith (Just (NodeByName (NodeName "n1"))) defaultHotThreadsOptions)
`shouldBe` ["_nodes", "n1", "hot_threads"]
it "interval renders as {n}{unit}" $
queries (getNodesHotThreadsWith Nothing defaultHotThreadsOptions {htoInterval = Just (TimeUnitMilliseconds, 500)})
`shouldBe` [("interval", Just "500ms")]
it "snapshots renders the bare integer" $
queries (getNodesHotThreadsWith Nothing defaultHotThreadsOptions {htoSnapshots = Just 5})
`shouldBe` [("snapshots", Just "5")]
it "type emitted under the type key only (no doc_type)" $
queries (getNodesHotThreadsWith Nothing defaultHotThreadsOptions {htoDocType = Just HotThreadsDocTypeCPU})
`shouldBe` [("type", Just "cpu")]
it "ignore_idle_threads renders as lowercase bool" $
queries (getNodesHotThreadsWith Nothing defaultHotThreadsOptions {htoIgnoreIdleThreads = Just False})
`shouldBe` [("ignore_idle_threads", Just "false")]
it "master_timeout renders as {n}{unit}" $
queries (getNodesHotThreadsWith Nothing defaultHotThreadsOptions {htoMasterTimeout = Just (TimeUnitSeconds, 5)})
`shouldBe` [("master_timeout", Just "5s")]
it "timeout renders as {n}{unit}" $
queries (getNodesHotThreadsWith Nothing defaultHotThreadsOptions {htoTimeout = Just (TimeUnitSeconds, 10)})
`shouldBe` [("timeout", Just "10s")]
it "all params together render in field order" $
let opts =
defaultHotThreadsOptions
{ htoThreads = Just (ThreadCount 1),
htoInterval = Just (TimeUnitMilliseconds, 500),
htoSnapshots = Just 5,
htoDocType = Just HotThreadsDocTypeWait,
htoIgnoreIdleThreads = Just True,
htoMasterTimeout = Just (TimeUnitSeconds, 5),
htoTimeout = Just (TimeUnitSeconds, 10)
}
in queries (getNodesHotThreadsWith Nothing opts)
`shouldBe` [ ("threads", Just "1"),
("interval", Just "500ms"),
("snapshots", Just "5"),
("type", Just "wait"),
("ignore_idle_threads", Just "true"),
("master_timeout", Just "5s"),
("timeout", Just "10s")
]
describe "HotThreadsDocType JSON" $ do
it "renders HotThreadsDocTypeCPU as \"cpu\"" $
encode HotThreadsDocTypeCPU `shouldBe` "\"cpu\""
it "renders HotThreadsDocTypeWait as \"wait\"" $
encode HotThreadsDocTypeWait `shouldBe` "\"wait\""
it "renders HotThreadsDocTypeBlock as \"block\"" $
encode HotThreadsDocTypeBlock `shouldBe` "\"block\""
it "parses \"cpu\" as HotThreadsDocTypeCPU" $
decode "\"cpu\"" `shouldBe` Just HotThreadsDocTypeCPU
it "parses \"wait\" as HotThreadsDocTypeWait" $
decode "\"wait\"" `shouldBe` Just HotThreadsDocTypeWait
it "parses \"block\" as HotThreadsDocTypeBlock" $
decode "\"block\"" `shouldBe` Just HotThreadsDocTypeBlock
it "round-trips every named constructor" $ do
decode (encode HotThreadsDocTypeCPU) `shouldBe` Just HotThreadsDocTypeCPU
decode (encode HotThreadsDocTypeWait) `shouldBe` Just HotThreadsDocTypeWait
decode (encode HotThreadsDocTypeBlock) `shouldBe` Just HotThreadsDocTypeBlock
it "rejects unknown strings (reject-unknown convention, matches NodeInfoMetric)" $
decode "\"future_kind\"" `shouldBe` (Nothing :: Maybe HotThreadsDocType)
it "encodes the Other escape hatch verbatim (Haskell-only, not decodable)" $
encode (OtherHotThreadsDocType "future_kind") `shouldBe` "\"future_kind\""
describe "ThreadPoolType FromJSON" $ do
it "parses \"resizable\" as ThreadPoolResizable" $
decode "\"resizable\"" `shouldBe` Just ThreadPoolResizable
it "parses \"scaling\" as ThreadPoolScaling" $
decode "\"scaling\"" `shouldBe` Just ThreadPoolScaling
it "parses \"fixed\" as ThreadPoolFixed" $
decode "\"fixed\"" `shouldBe` Just ThreadPoolFixed
it "parses \"cached\" as ThreadPoolCached" $
decode "\"cached\"" `shouldBe` Just ThreadPoolCached
it "parses \"fixed_auto_queue_size\" as ThreadPoolFixedAutoQueueSize" $
decode "\"fixed_auto_queue_size\"" `shouldBe` Just ThreadPoolFixedAutoQueueSize
it "rejects unknown thread pool types" $
decode "\"bogus\"" `shouldBe` (Nothing :: Maybe ThreadPoolType)
-- Endpoint-shape tests for the metric-filter variants. These are pure
-- (no live ES) — they pin the URL path that 'getNodesInfoWith' /
-- 'getNodesStatsWith' emit so the wire format cannot drift silently.
describe "getNodesInfoWith endpoint shape" $ do
let path req = getRawEndpoint (bhRequestEndpoint req)
queries req = getRawEndpointQueries (bhRequestEndpoint req)
it "no metrics -> identical to getNodesInfo (LocalNode)" $
path (getNodesInfoWith LocalNode defaultNodeInfoOptions)
`shouldBe` path (getNodesInfo LocalNode)
it "no metrics -> identical to getNodesInfo (AllNodes)" $
path (getNodesInfoWith AllNodes defaultNodeInfoOptions)
`shouldBe` path (getNodesInfo AllNodes)
it "no metrics -> identical to getNodesInfo (NodeList)" $
let sel = NodeList (NodeByName (NodeName "node-1") :| [])
in path (getNodesInfoWith sel defaultNodeInfoOptions) `shouldBe` path (getNodesInfo sel)
it "LocalNode + [jvm, os] -> /_nodes/_local/info/jvm,os" $
path (getNodesInfoWith LocalNode defaultNodeInfoOptions {nioMetrics = Just [NodeInfoMetricJVM, NodeInfoMetricOS]})
`shouldBe` ["_nodes", "_local", "info", "jvm,os"]
it "AllNodes + [jvm] -> /_nodes/_all/info/jvm" $
path (getNodesInfoWith AllNodes defaultNodeInfoOptions {nioMetrics = Just [NodeInfoMetricJVM]})
`shouldBe` ["_nodes", "_all", "info", "jvm"]
it "NodeList + [jvm] -> /_nodes/{sel}/info/jvm" $
let sel = NodeList (NodeByName (NodeName "node-1") :| [NodeByFullNodeId (FullNodeId "abc")])
in path (getNodesInfoWith sel defaultNodeInfoOptions {nioMetrics = Just [NodeInfoMetricJVM]})
`shouldBe` ["_nodes", "node-1,abc", "info", "jvm"]
it "NodeByHost selector renders host as-is" $
let sel = NodeList (NodeByHost (Server "10.0.0.1") :| [])
in path (getNodesInfoWith sel defaultNodeInfoOptions {nioMetrics = Just [NodeInfoMetricHTTP]})
`shouldBe` ["_nodes", "10.0.0.1", "info", "http"]
it "NodeByAttribute renders as key:value" $
let sel = NodeList (NodeByAttribute (NodeAttrName "rack") "r1" :| [])
in path (getNodesInfoWith sel defaultNodeInfoOptions {nioMetrics = Just [NodeInfoMetricOS]})
`shouldBe` ["_nodes", "rack:r1", "info", "os"]
it "OtherNodeInfoMetric renders verbatim" $
path (getNodesInfoWith LocalNode defaultNodeInfoOptions {nioMetrics = Just [OtherNodeInfoMetric "custom_metric"]})
`shouldBe` ["_nodes", "_local", "info", "custom_metric"]
it "multiple metrics comma-joined in declaration order" $
path
( getNodesInfoWith
LocalNode
defaultNodeInfoOptions {nioMetrics = Just [NodeInfoMetricPlugins, NodeInfoMetricIngest, NodeInfoMetricJVM]}
)
`shouldBe` ["_nodes", "_local", "info", "plugins,ingest,jvm"]
it "default options -> no query string" $
queries (getNodesInfoWith LocalNode defaultNodeInfoOptions) `shouldBe` []
it "flat_settings renders as lowercase bool" $
queries (getNodesInfoWith LocalNode defaultNodeInfoOptions {nioFlatSettings = Just True})
`shouldBe` [("flat_settings", Just "true")]
it "master_timeout renders as {n}{unit}" $
queries (getNodesInfoWith LocalNode defaultNodeInfoOptions {nioMasterTimeout = Just (TimeUnitSeconds, 5)})
`shouldBe` [("master_timeout", Just "5s")]
it "timeout renders as {n}{unit}" $
queries (getNodesInfoWith LocalNode defaultNodeInfoOptions {nioTimeout = Just (TimeUnitSeconds, 10)})
`shouldBe` [("timeout", Just "10s")]
it "all params together render in field order" $
let opts =
defaultNodeInfoOptions
{ nioFlatSettings = Just True,
nioMasterTimeout = Just (TimeUnitSeconds, 5),
nioTimeout = Just (TimeUnitSeconds, 10)
}
in queries (getNodesInfoWith LocalNode opts)
`shouldBe` [ ("flat_settings", Just "true"),
("master_timeout", Just "5s"),
("timeout", Just "10s")
]
describe "getNodesStatsWith endpoint shape" $ do
let path req = getRawEndpoint (bhRequestEndpoint req)
queries req = getRawEndpointQueries (bhRequestEndpoint req)
it "no metrics -> identical to getNodesStats (LocalNode)" $
path (getNodesStatsWith LocalNode defaultNodeStatsOptions)
`shouldBe` path (getNodesStats LocalNode)
it "no metrics -> identical to getNodesStats (AllNodes)" $
path (getNodesStatsWith AllNodes defaultNodeStatsOptions)
`shouldBe` path (getNodesStats AllNodes)
it "LocalNode + [jvm, os] -> /_nodes/_local/stats/jvm,os" $
path (getNodesStatsWith LocalNode defaultNodeStatsOptions {nsoMetrics = Just [NodeStatsMetricJVM, NodeStatsMetricOS]})
`shouldBe` ["_nodes", "_local", "stats", "jvm,os"]
it "AllNodes + [fs] -> /_nodes/_all/stats/fs" $
path (getNodesStatsWith AllNodes defaultNodeStatsOptions {nsoMetrics = Just [NodeStatsMetricFS]})
`shouldBe` ["_nodes", "_all", "stats", "fs"]
it "NodeList + [jvm, breakers] preserves selector rendering" $
let sel = NodeList (NodeByName (NodeName "n1") :| [])
in path (getNodesStatsWith sel defaultNodeStatsOptions {nsoMetrics = Just [NodeStatsMetricJVM, NodeStatsMetricBreakers]})
`shouldBe` ["_nodes", "n1", "stats", "jvm,breakers"]
it "OtherNodeStatsMetric renders verbatim" $
path (getNodesStatsWith LocalNode defaultNodeStatsOptions {nsoMetrics = Just [OtherNodeStatsMetric "indexing_pressure"]})
`shouldBe` ["_nodes", "_local", "stats", "indexing_pressure"]
it "default options -> no query string" $
queries (getNodesStatsWith LocalNode defaultNodeStatsOptions) `shouldBe` []
it "completion_fields renders verbatim" $
queries (getNodesStatsWith LocalNode defaultNodeStatsOptions {nsoCompletionFields = Just "text,*_suggest"})
`shouldBe` [("completion_fields", Just "text,*_suggest")]
it "fielddata_fields renders verbatim" $
queries (getNodesStatsWith LocalNode defaultNodeStatsOptions {nsoFielddataFields = Just "text"})
`shouldBe` [("fielddata_fields", Just "text")]
it "fields renders verbatim" $
queries (getNodesStatsWith LocalNode defaultNodeStatsOptions {nsoFields = Just "*"})
`shouldBe` [("fields", Just "*")]
it "groups renders verbatim" $
queries (getNodesStatsWith LocalNode defaultNodeStatsOptions {nsoGroups = Just "g1,g2"})
`shouldBe` [("groups", Just "g1,g2")]
it "level renders via renderNodeStatsLevel" $
queries (getNodesStatsWith LocalNode defaultNodeStatsOptions {nsoLevel = Just NodeStatsLevelShards})
`shouldBe` [("level", Just "shards")]
it "level=cluster renders via renderNodeStatsLevel" $
queries (getNodesStatsWith LocalNode defaultNodeStatsOptions {nsoLevel = Just NodeStatsLevelCluster})
`shouldBe` [("level", Just "cluster")]
it "types renders verbatim" $
queries (getNodesStatsWith LocalNode defaultNodeStatsOptions {nsoTypes = Just "_doc"})
`shouldBe` [("types", Just "_doc")]
it "master_timeout renders as {n}{unit}" $
queries (getNodesStatsWith LocalNode defaultNodeStatsOptions {nsoMasterTimeout = Just (TimeUnitSeconds, 5)})
`shouldBe` [("master_timeout", Just "5s")]
it "timeout renders as {n}{unit}" $
queries (getNodesStatsWith LocalNode defaultNodeStatsOptions {nsoTimeout = Just (TimeUnitSeconds, 10)})
`shouldBe` [("timeout", Just "10s")]
it "include_segment_file_sizes renders as lowercase bool" $
queries (getNodesStatsWith LocalNode defaultNodeStatsOptions {nsoIncludeSegmentFileSizes = Just True})
`shouldBe` [("include_segment_file_sizes", Just "true")]
it "include_unloaded_segments renders as lowercase bool" $
queries (getNodesStatsWith LocalNode defaultNodeStatsOptions {nsoIncludeUnloadedSegments = Just True})
`shouldBe` [("include_unloaded_segments", Just "true")]
it "all params together render in field order" $
let opts =
defaultNodeStatsOptions
{ nsoCompletionFields = Just "cf",
nsoFielddataFields = Just "fdf",
nsoFields = Just "f",
nsoGroups = Just "g",
nsoLevel = Just NodeStatsLevelIndices,
nsoTypes = Just "t",
nsoMasterTimeout = Just (TimeUnitSeconds, 5),
nsoTimeout = Just (TimeUnitSeconds, 10),
nsoIncludeSegmentFileSizes = Just True,
nsoIncludeUnloadedSegments = Just False
}
in queries (getNodesStatsWith LocalNode opts)
`shouldBe` [ ("completion_fields", Just "cf"),
("fielddata_fields", Just "fdf"),
("fields", Just "f"),
("groups", Just "g"),
("level", Just "indices"),
("types", Just "t"),
("master_timeout", Just "5s"),
("timeout", Just "10s"),
("include_segment_file_sizes", Just "true"),
("include_unloaded_segments", Just "false")
]
describe "getNodesUsageWith endpoint shape" $ do
let path req = getRawEndpoint (bhRequestEndpoint req)
queries req = getRawEndpointQueries (bhRequestEndpoint req)
it "no metrics -> identical to getNodesUsage (LocalNode)" $
path (getNodesUsageWith LocalNode defaultNodeUsageOptions)
`shouldBe` path (getNodesUsage LocalNode)
it "no metrics -> identical to getNodesUsage (AllNodes)" $
path (getNodesUsageWith AllNodes defaultNodeUsageOptions)
`shouldBe` path (getNodesUsage AllNodes)
it "LocalNode + [RestActions] -> /_nodes/_local/usage/rest_actions" $
path (getNodesUsageWith LocalNode defaultNodeUsageOptions {nuoMetrics = Just [NodeUsageMetricRestActions]})
`shouldBe` ["_nodes", "_local", "usage", "rest_actions"]
it "AllNodes no metrics -> /_nodes/_all/usage" $
path (getNodesUsageWith AllNodes defaultNodeUsageOptions)
`shouldBe` ["_nodes", "_all", "usage"]
it "NodeList + [RestActions] -> /_nodes/{sel}/usage/rest_actions" $
let sel = NodeList (NodeByName (NodeName "n1") :| [NodeByFullNodeId (FullNodeId "abc")])
in path (getNodesUsageWith sel defaultNodeUsageOptions {nuoMetrics = Just [NodeUsageMetricRestActions]})
`shouldBe` ["_nodes", "n1,abc", "usage", "rest_actions"]
it "OtherNodeUsageMetric renders verbatim" $
path (getNodesUsageWith LocalNode defaultNodeUsageOptions {nuoMetrics = Just [OtherNodeUsageMetric "custom_metric"]})
`shouldBe` ["_nodes", "_local", "usage", "custom_metric"]
it "default options -> no query string" $
queries (getNodesUsageWith LocalNode defaultNodeUsageOptions) `shouldBe` []
it "master_timeout renders as {n}{unit}" $
queries (getNodesUsageWith LocalNode defaultNodeUsageOptions {nuoMasterTimeout = Just (TimeUnitSeconds, 5)})
`shouldBe` [("master_timeout", Just "5s")]
it "timeout renders as {n}{unit}" $
queries (getNodesUsageWith LocalNode defaultNodeUsageOptions {nuoTimeout = Just (TimeUnitSeconds, 10)})
`shouldBe` [("timeout", Just "10s")]
it "all params together render in field order" $
let opts =
defaultNodeUsageOptions
{ nuoMasterTimeout = Just (TimeUnitSeconds, 5),
nuoTimeout = Just (TimeUnitSeconds, 10)
}
in queries (getNodesUsageWith LocalNode opts)
`shouldBe` [ ("master_timeout", Just "5s"),
("timeout", Just "10s")
]
-- Endpoint-shape tests for 'reloadSecureSettings'. Pure (no live ES)
-- — they pin the HTTP method, URL path, query string and body so the
-- wire format cannot drift silently. Mirrors the precedent set by
-- @startILM@\/@stopILM@ in @Test/ILMSpec.hs@ and @cleanupSnapshotRepo@
-- in @Test/SnapshotsSpec.hs@.
describe "reloadSecureSettings endpoint shape" $ do
let mkReq = reloadSecureSettings
assertShape req expectedPath = do
bhRequestMethod req `shouldBe` "POST"
getRawEndpoint (bhRequestEndpoint req) `shouldBe` expectedPath
getRawEndpointQueries (bhRequestEndpoint req) `shouldBe` []
bhRequestBody req `shouldBe` Just ""
it "Nothing -> POST /_nodes/reload_secure_settings, empty body, no queries" $
assertShape (mkReq Nothing) ["_nodes", "reload_secure_settings"]
it "Just LocalNode -> POST /_nodes/_local/reload_secure_settings" $
assertShape (mkReq (Just LocalNode)) ["_nodes", "_local", "reload_secure_settings"]
it "Just AllNodes -> POST /_nodes/_all/reload_secure_settings" $
assertShape (mkReq (Just AllNodes)) ["_nodes", "_all", "reload_secure_settings"]
it "Just (NodeList ...) renders selectors comma-joined" $ do
let sel = NodeList (NodeByName (NodeName "n1") :| [NodeByFullNodeId (FullNodeId "abc")])
assertShape (mkReq (Just sel)) ["_nodes", "n1,abc", "reload_secure_settings"]
it "Just (NodeList NodeByAttribute ...) renders key:value" $ do
let sel = NodeList (NodeByAttribute (NodeAttrName "rack") "r1" :| [])
assertShape (mkReq (Just sel)) ["_nodes", "rack:r1", "reload_secure_settings"]
it "Just (NodeList NodeByHost ...) renders host verbatim" $ do
let sel = NodeList (NodeByHost (Server "10.0.0.1") :| [])
assertShape (mkReq (Just sel)) ["_nodes", "10.0.0.1", "reload_secure_settings"]
describe "NodeInfoMetric JSON" $ do
it "renders NodeInfoMetricJVM as \"jvm\"" $
encode NodeInfoMetricJVM `shouldBe` "\"jvm\""
it "renders NodeInfoMetricThreadPool as \"thread_pool\"" $
encode NodeInfoMetricThreadPool `shouldBe` "\"thread_pool\""
it "rejects unknown metrics" $
decode "\"bogus\"" `shouldBe` (Nothing :: Maybe NodeInfoMetric)
mapM_ roundTripInfo allNodeInfoMetricsForRoundTrip
describe "NodeStatsMetric JSON" $ do
it "renders NodeStatsMetricIndices as \"indices\"" $
encode NodeStatsMetricIndices `shouldBe` "\"indices\""
it "renders NodeStatsMetricAdaptiveSelection as \"adaptive_selection\"" $
encode NodeStatsMetricAdaptiveSelection `shouldBe` "\"adaptive_selection\""
it "rejects unknown metrics" $
decode "\"bogus\"" `shouldBe` (Nothing :: Maybe NodeStatsMetric)
mapM_ roundTripStats allNodeStatsMetricsForRoundTrip
describe "NodeUsageMetric JSON" $ do
it "renders NodeUsageMetricRestActions as \"rest_actions\"" $
encode NodeUsageMetricRestActions `shouldBe` "\"rest_actions\""
it "rejects unknown metrics" $
decode "\"bogus\"" `shouldBe` (Nothing :: Maybe NodeUsageMetric)
mapM_ roundTripUsage allNodeUsageMetricsForRoundTrip
where
roundTripInfo m =
it ("round-trips " <> show m) $
decode (encode m) `shouldBe` (Just m :: Maybe NodeInfoMetric)
roundTripStats m =
it ("round-trips " <> show m) $
decode (encode m) `shouldBe` (Just m :: Maybe NodeStatsMetric)
roundTripUsage m =
it ("round-trips " <> show m) $
decode (encode m) `shouldBe` (Just m :: Maybe NodeUsageMetric)
-- Exhaustive lists of the named constructors (excluding the open-ended
-- 'Other*Metric Text' escape hatches). Replaces the previous
-- @[minBound .. maxBound]@ enumeration, which stopped being derivable
-- when the escape hatches landed. Listed inline here to keep the test
-- self-contained.
allNodeInfoMetricsForRoundTrip =
[ NodeInfoMetricSettings,
NodeInfoMetricOS,
NodeInfoMetricProcess,
NodeInfoMetricJVM,
NodeInfoMetricThreadPool,
NodeInfoMetricTransport,
NodeInfoMetricHTTP,
NodeInfoMetricPlugins,
NodeInfoMetricIngest,
NodeInfoMetricAggregations,
NodeInfoMetricIndices,
NodeInfoMetricDiscovery,
NodeInfoMetricScripting,
NodeInfoMetricAction
]
allNodeStatsMetricsForRoundTrip =
[ NodeStatsMetricIndices,
NodeStatsMetricOS,
NodeStatsMetricProcess,
NodeStatsMetricJVM,
NodeStatsMetricThreadPool,
NodeStatsMetricFS,
NodeStatsMetricTransport,
NodeStatsMetricHTTP,
NodeStatsMetricBreakers,
NodeStatsMetricScript,
NodeStatsMetricDiscovery,
NodeStatsMetricIngest,
NodeStatsMetricAdaptiveSelection,
NodeStatsMetricScriptCache,
NodeStatsMetricIndexingPressure,
NodeStatsMetricCgroup
]
allNodeUsageMetricsForRoundTrip =
[ NodeUsageMetricRestActions,
NodeUsageMetricAggregations
]
-- | Extract the top-level keys of the first (and typically only) entry
-- under @.nodes@ in a @_nodes/{sel}/info@ or @/_nodes/{sel}/stats@
-- response body. Returns the empty list if the body doesn't match the
-- expected shape.
nodeMetricKeys :: BL.ByteString -> [T.Text]
nodeMetricKeys body =
case decode body :: Maybe Value of
Just topVal
| Just nodeData <- firstNodeObject topVal ->
toText <$> KM.keys nodeData
_ -> []
where
firstNodeObject val = do
nodesObj <- parseMaybe (withObject "top" (.: "nodes")) val
case KM.toList nodesObj of
(_, Object fields) : _ -> Just fields
_ -> Nothing