packages feed

salmon-ops-recipes-0.1.0.0: test/Test/EtcdSpec.hs

{-# LANGUAGE OverloadedStrings #-}

{- | Layer 0 coverage for "Salmon.Builtin.Nodes.Etcd": rendering, the
membership verdict and the seed decision. The three-VM Layer 3 case (seed,
then a member restart without re-bootstrapping) needs the Patroni harness and
is not here.
-}
module Test.EtcdSpec (tests) where

import Data.List (isInfixOf)
import qualified Data.Text as Text
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertBool, assertEqual, assertFailure, testCase)

import Salmon.Actions.UpDown (CheckResult (..))
import Salmon.Builtin.Nodes.Etcd

tests :: TestTree
tests =
    testGroup
        "Salmon.Builtin.Nodes.Etcd"
        [ testCase "the config seeds with state new and lists every member" $ do
            assertBool cfgText ("initial-cluster-state: new" `isInfixOf` cfgText)
            assertBool cfgText ("initial-cluster: a=https://10.0.0.1:2380,b=https://10.0.0.2:2380,c=https://10.0.0.3:2380" `isInfixOf` cfgText)
            assertBool cfgText ("name: b" `isInfixOf` cfgText)
        , testCase "the initial cluster does not depend on declaration order" $
            assertEqual "" (renderInitialCluster members) (renderInitialCluster (reverse members))
        , testCase "TLS paths are used as given" $
            assertBool cfgText ("cert-file: /pki/peer.crt" `isInfixOf` cfgText)
        , testCase "the member list parses, and an unstarted member has no name" $
            assertEqual
                ""
                (Right [Listed "a" ["https://10.0.0.1:2380"], Listed "" ["https://10.0.0.9:2380"]])
                (parseMemberList "{\"header\":{},\"members\":[{\"ID\":1,\"name\":\"a\",\"peerURLs\":[\"https://10.0.0.1:2380\"]},{\"ID\":2,\"peerURLs\":[\"https://10.0.0.9:2380\"]}]}")
        , testCase "garbage is a Left, not an exception" $
            assertBool "" (either (const True) (const False) (parseMemberList "nope"))
        , testCase "membership matching the declaration is Success" $
            assertEqual "" Success (interpretMembers members (fmap listedOf members))
        , testCase "a missing member is a Failure naming it" $
            case interpretMembers members (fmap listedOf (take 2 members)) of
                Failure why -> assertBool (Text.unpack why) ("10.0.0.3" `isInfixOf` Text.unpack why)
                other -> assertFailure (show other)
        , testCase "an unexpected member is a Failure too" $
            case interpretMembers (take 2 members) (fmap listedOf members) of
                Failure why -> assertBool (Text.unpack why) ("unexpected" `isInfixOf` Text.unpack why)
                other -> assertFailure (show other)
        , testCase "seed: nobody answering is a seed" $
            assertEqual "" True (ok (seedDecision self [(m, Nothing) | m <- others]))
        , testCase "seed: a cluster that lists us is a late bootstrap member, not a refusal" $
            assertEqual "" True (ok (seedDecision self [(head others, Just (fmap listedOf members))]))
        , testCase "seed: a cluster that does not list us is refused" $
            assertEqual "" False (ok (seedDecision self [(head others, Just (fmap listedOf others))]))
        ]
  where
    cfgText = Text.unpack (renderConfig cfg)
    ok = either (const False) (const True)

members :: [Member]
members =
    [ Member "a" "https://10.0.0.1:2380" "https://10.0.0.1:2379"
    , Member "b" "https://10.0.0.2:2380" "https://10.0.0.2:2379"
    , Member "c" "https://10.0.0.3:2380" "https://10.0.0.3:2379"
    ]

self :: Member
self = members !! 1

others :: [Member]
others = filter (/= self) members

listedOf :: Member -> Listed
listedOf m = Listed (member_name m) [member_peer_url m]

cfg :: EtcdConfig
cfg =
    EtcdConfig
        { etcd_self = self
        , etcd_cluster = members
        , etcd_cluster_token = "tok"
        , etcd_data_dir = "/var/lib/etcd"
        , etcd_config_file = "/etc/etcd/etcd.yaml"
        , etcd_user = "etcd"
        , etcd_client_tls = TlsFiles "/pki/ca.crt" "/pki/client.crt" "/pki/client.key"
        , etcd_peer_tls = TlsFiles "/pki/ca.crt" "/pki/peer.crt" "/pki/peer.key"
        , etcd_ready_timeout_seconds = 30
        }