packages feed

om-legion-6.9.0.3: test/Test/OM/Legion.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}

module Test.OM.Legion (
  S(..),
  Request(..),
  Response(..),
  Op(..),
  k8sConfig,
  Client,
  send,
  makeClient,
  makeClientLocal,
) where


import Control.Monad.IO.Class (MonadIO(liftIO))
import Control.Monad.Logger.CallStack (MonadLoggerIO, logInfo)
import Data.Aeson (KeyValue((.=)), ToJSON, object)
import Data.Binary (Binary)
import Data.CRDT.EventFold (Event(Output, State, apply),
  EventResult(Pure), EventFold)
import Data.Default.Class (Default(def))
import Data.Text (Text)
import GHC.Generics (Generic)
import Language.Haskell.TH.Syntax (addDependentFile)
import Numeric.Natural (Natural)
import OM.Kubernetes (Namespace(Namespace))
import OM.Legion (ClusterName, Peer, Stats)
import OM.Show (showt)
import OM.Socket (AddressDescription(AddressDescription), connectServer)
import qualified Text.Mustache as Mustache (Template, substitute)
import qualified Text.Mustache.Compile as Mustache (embedSingleTemplate)


newtype S = S Int
  deriving newtype (Enum, Binary, Show, ToJSON, Eq, Num)
instance Default S where
  def = 0


data Request
  = OpReq Op
  | ReadState
  | ReadStats
  deriving stock (Generic, Show, Read)
  deriving anyclass (Binary)


data Response
  = OpR (Output Op)
  | ReadStateR (EventFold ClusterName Peer Op)
  | ReadStatsR Stats
  deriving stock (Generic, Show, Eq)
  deriving anyclass (Binary, ToJSON)


data Op
  = Inc
  | Dec
  | Get
  deriving stock (Eq, Generic, Show, Read)
  deriving anyclass (Binary, ToJSON)
instance Event Peer Op where
  type Output Op = (S, S)
  type State Op = S

  apply op val =
    let
      newVal :: S
      newVal =
        case op of
          Inc -> succ val
          Dec -> pred val
          Get -> val
    in
      Pure (val, newVal) newVal


k8sConfig
  :: String {- ^ cluster name.  -}
  -> String {- ^ Docker image name.  -}
  -> Text {- ^ k8s config.  -}
k8sConfig name image =
    Mustache.substitute
      template
      (object ["name" .= name, "image" .= image])
  where
    template :: Mustache.Template
    template =
      $(do
        addDependentFile "test/k8s/k8s.mustache"
        Mustache.embedSingleTemplate "test/k8s/k8s.mustache"
      )


newtype Client = Client
  { unClient :: Request -> IO Response
  }


send :: (MonadIO m) => Client -> Request -> m Response
send client = liftIO . unClient client


makeClient :: (MonadLoggerIO m) => Namespace -> Natural -> m Client
makeClient (Namespace namespace) ord = do
  let
    targetPeer =
      namespace <> "-" <> showt ord
      <> "." <> namespace
      <> "." <> namespace
      <> ".svc.cluster.local"
  logInfo $ "Target: " <> showt targetPeer
  Client <$>
    connectServer
      (AddressDescription (targetPeer <> ":9999"))
      Nothing


makeClientLocal :: (MonadLoggerIO m) => m Client
makeClientLocal= do
  Client <$>
    connectServer
      (AddressDescription ("localhost:9999"))
      Nothing