module Raft.Config where import Protolude import Numeric.Natural (Natural) import Network.Socket import Raft.Types import System.Random (randomIO) -- | Configuration of a node in the cluster data RaftNodeConfig = RaftNodeConfig { raftConfigNodeId :: NodeId -- ^ Node id of the running node , raftConfigNodeIds :: NodeIds -- ^ Set of all other node ids in the cluster , raftConfigElectionTimeout :: (Natural, Natural) -- ^ Range of times an election timeout can take , raftConfigHeartbeatTimeout :: Natural -- ^ Heartbeat timeout timer , raftConfigStorageState :: StorageState -- ^ Create a fresh DB or read from existing } deriving (Show) data StorageState = New | Existing deriving Show data OptionalRaftNodeConfig = OptionalRaftNodeConfig { raftConfigMetricsPort :: Maybe PortNumber , raftConfigTimerSeed :: Maybe Int } deriving (Show) defaultOptionalRaftNodeConfig :: OptionalRaftNodeConfig defaultOptionalRaftNodeConfig = OptionalRaftNodeConfig Nothing Nothing data ConfigError = InvalidMetricsPort | NoFreePortAvailable deriving (Show) resolveMetricsPort :: Maybe PortNumber -> IO (Maybe PortNumber) resolveMetricsPort mPort = case mPort of Nothing -> pure Nothing Just port -> do eMetricsPort <- resolveMetricsPortE port case eMetricsPort of Left err -> panic ("Error in raft node config: " <> show err) Right port -> pure (Just port) -- | If the user specifies a port to fork the EKG server on, make sure the port -- is open and return the valid port number. If the user does not specify a port -- to run the monitoring server on, return Nothing. resolveMetricsPortE :: PortNumber -> IO (Either ConfigError PortNumber) resolveMetricsPortE port | port > 0 && port <= 65535 = do let hints = defaultHints { addrFlags = [AI_NUMERICHOST, AI_NUMERICSERV], addrSocketType = Stream } addrs <- getAddrInfo (Just hints) (Just "127.0.0.1") (Just (show port)) case addrs of [] -> pure (Left NoFreePortAvailable) addr:_ -> do sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr) Network.Socket.bind sock (addrAddress addr) freePort <- socketPort sock close sock pure (Right freePort) | otherwise = pure (Left InvalidMetricsPort) resolveTimerSeed :: Maybe Int -> IO Int resolveTimerSeed mSeed = do case mSeed of Just seed -> pure seed Nothing -> randomIO