Skip to content

Commit

Permalink
New cardano-cli ping command.
Browse files Browse the repository at this point in the history
  • Loading branch information
newhoggy committed Nov 30, 2022
1 parent 15556f3 commit afa8dd5
Show file tree
Hide file tree
Showing 6 changed files with 646 additions and 2 deletions.
1 change: 0 additions & 1 deletion cardano-api/src/Cardano/Api/StakePoolMetadata.hs
Expand Up @@ -42,7 +42,6 @@ import Cardano.Ledger.Crypto (StandardCrypto)

import qualified Cardano.Ledger.Keys as Shelley


-- ----------------------------------------------------------------------------
-- Stake pool metadata
--
Expand Down
7 changes: 7 additions & 0 deletions cardano-cli/cardano-cli.cabal
Expand Up @@ -90,6 +90,9 @@ library
Cardano.CLI.Shelley.Run.Read
Cardano.CLI.Shelley.Run.Validate

Cardano.CLI.Ping
Cardano.CLI.Ping.Lib

Cardano.CLI.TopHandler

other-modules: Paths_cardano_cli
Expand Down Expand Up @@ -125,8 +128,10 @@ library
, directory
, filepath
, formatting
, io-classes
, iproute
, network
, network-mux
, optparse-applicative-fork
, ouroboros-consensus
, ouroboros-consensus-byron
Expand All @@ -141,7 +146,9 @@ library
, set-algebra
, split
, strict-containers
, strict-stm
, text
, tdigest
, time
, transformers
, transformers-except
Expand Down
8 changes: 7 additions & 1 deletion cardano-cli/src/Cardano/CLI/Parsers.hs
@@ -1,16 +1,18 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}

module Cardano.CLI.Parsers
( opts
, pref
) where

import Cardano.Prelude
import Cardano.CLI.Byron.Parsers (backwardsCompatibilityCommands, parseByronCommands)
import Cardano.CLI.Ping (parsePingCmd)
import Cardano.CLI.Render (customRenderHelp)
import Cardano.CLI.Run (ClientCommand (..))
import Cardano.CLI.Shelley.Parsers (parseShelleyCommands)
import Cardano.Prelude
import Options.Applicative
import Prelude (String)

Expand Down Expand Up @@ -45,6 +47,7 @@ parseClientCommand =
-- so we list it first.
[ parseShelley
, parseByron
, parsePing
, parseDeprecatedShelleySubcommand
, backwardsCompatibilityCommands
, parseDisplayVersion opts
Expand All @@ -62,6 +65,9 @@ parseByron =
parseByronCommands
]

parsePing :: Parser ClientCommand
parsePing = CliPingCommand <$> parsePingCmd

-- | Parse Shelley-related commands at the top level of the CLI.
parseShelley :: Parser ClientCommand
parseShelley = ShelleyCommand <$> parseShelleyCommands
Expand Down
158 changes: 158 additions & 0 deletions cardano-cli/src/Cardano/CLI/Ping.hs
@@ -0,0 +1,158 @@
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}

module Cardano.CLI.Ping
( PingCmd(..)
, PingCmdError(..)
, renderPingCmdError
, runPingCmd
, parsePingCmd
) where

import Control.Applicative (Applicative (..), optional)
import Control.Exception (SomeException)
import Control.Monad (Monad (..), forM, mapM, mapM_, unless, when)
import Control.Monad.Class.MonadAsync (MonadAsync (async, wait, waitCatch))
import Control.Monad.Class.MonadSTM.Strict
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Except (ExceptT)
import Control.Tracer (Tracer (..))
import Data.Bool ((&&))
import Data.Either (Either (..))
import Data.Function (($), (.))
import Data.Functor ((<$>))
import Data.List (foldl')
import Data.Maybe (Maybe (..), isNothing)
import Data.Monoid (mconcat)
import Data.Semigroup ((<>))
import Data.String (String)
import Data.Text (Text)
import GHC.Enum (Bounded (..))
import Network.Socket (AddrInfo)
import System.Exit (ExitCode (ExitFailure))
import System.IO (IO)
import Cardano.CLI.Ping.Lib

import qualified Data.List as L
import qualified Network.Socket as Socket
import qualified Options.Applicative as Opt
import qualified Prettyprinter as PP
import qualified System.Exit as IO
import qualified System.IO as IO

runPingCmd :: PingCmd -> ExceptT PingCmdError IO ()
runPingCmd options = do
let hints = Socket.defaultHints { Socket.addrSocketType = Socket.Stream }

msgQueue <- liftIO newEmptyTMVarIO

when (isNothing (pingCmdHost options) && isNothing (pingCmdUnixSock options)) $ do
liftIO $ IO.putStrLn "Specify host/ip with '-h <hostname>' or a unix socket with -u <file name>"
liftIO $ IO.exitWith (ExitFailure 1)

(addresses, versions) <- case pingCmdUnixSock options of
Nothing -> do
addrs <- liftIO $ Socket.getAddrInfo (Just hints) (pingCmdHost options) (Just (pingCmdPort options))
return (addrs, supportedNodeToNodeVersions $ pingCmdMagic options)
Just fname ->
return
( [ Socket.AddrInfo [] Socket.AF_UNIX Socket.Stream
Socket.defaultProtocol (Socket.SockAddrUnix fname)
Nothing
]
, supportedNodeToClientVersions $ pingCmdMagic options
)

laid <- liftIO . async $ logger msgQueue $ pingCmdJson options
caids <- forM addresses $ liftIO . async . pingClient (Tracer $ doLog msgQueue) options versions
res <- L.zip addresses <$> mapM (liftIO . waitCatch) caids
liftIO $ doLog msgQueue LogEnd
liftIO $ wait laid
case foldl' partition ([],[]) res of
([], _) -> liftIO IO.exitSuccess
(es, []) -> do
mapM_ (liftIO . IO.hPrint IO.stderr) es
liftIO $ IO.exitWith (ExitFailure 1)
(es, _) -> do
unless (pingCmdQuiet options) $ mapM_ (liftIO . IO.hPrint IO.stderr) es
liftIO IO.exitSuccess

where
partition :: ([(AddrInfo, SomeException)], [AddrInfo])
-> (AddrInfo, Either SomeException ())
-> ([(AddrInfo, SomeException)], [AddrInfo])
partition (es, as) (a, Left e) = ((a, e) : es, as)
partition (es, as) (a, Right _) = (es, a : as)

doLog :: StrictTMVar IO LogMsg -> LogMsg -> IO ()
doLog msgQueue msg = atomically $ putTMVar msgQueue msg

renderPingCmdError :: PingCmdError -> Text
renderPingCmdError _err = "TODO"

parsePingCmd :: Opt.Parser PingCmd
parsePingCmd = Opt.hsubparser $ mconcat
[ Opt.metavar "ping"
, Opt.command "ping" $ Opt.info pPing $ Opt.progDescDoc $ Just $ mconcat
[ PP.pretty @String "Ping a cardano node either using node-to-node or node-to-client protocol. "
, PP.pretty @String "It negotiates a handshake and keep sending keep alive messages."
]
]

pPing :: Opt.Parser PingCmd
pPing = PingCmd
<$> Opt.option Opt.auto
( Opt.long "count"
<> Opt.short 'c'
<> Opt.metavar "COUNT"
<> Opt.help
( "Stop after sending count requests and receiving count responses. "
<> "If this option is not specified, ping will operate until interrupted. "
)
<> Opt.value maxBound
)
<*> optional
( Opt.option Opt.auto
( Opt.long "host"
<> Opt.short 'h'
<> Opt.metavar "HOST"
<> Opt.help "Hostname/IP, e.g. relay.iohk.example."
)
)
<*> optional
( Opt.option Opt.auto
( Opt.long "unixsock"
<> Opt.short 'u'
<> Opt.metavar "SOCKET"
<> Opt.help "Unix socket, e.g. file.socket."
)
)
<*> Opt.option Opt.auto
( Opt.long "port"
<> Opt.short 'p'
<> Opt.metavar "PORT"
<> Opt.help "Port number, e.g. 1234."
<> Opt.value "3001"
)
<*> Opt.option Opt.auto
( Opt.long "magic"
<> Opt.short 'm'
<> Opt.metavar "MAGIC"
<> Opt.help "Network magic."
<> Opt.value mainnetMagic
)
<*> Opt.switch
( Opt.long "json"
<> Opt.short 'j'
<> Opt.help "JSON output flag."
)
<*> Opt.switch
( Opt.long "quiet"
<> Opt.short 'q'
<> Opt.help "Quiet flag, CSV/JSON only output"
)

0 comments on commit afa8dd5

Please sign in to comment.