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 Mar 16, 2023
1 parent d157923 commit 04ea9ee
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 8 deletions.
19 changes: 12 additions & 7 deletions cardano-cli/cardano-cli.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ library
Cardano.CLI.Shelley.Run.Read
Cardano.CLI.Shelley.Run.Validate

Cardano.CLI.Ping

Cardano.CLI.TopHandler

other-modules: Paths_cardano_cli
Expand All @@ -100,35 +102,38 @@ library
, aeson-pretty >= 0.8.5
, ansi-terminal
, attoparsec
, base16-bytestring >= 1.0
, bech32 >= 1.1.0
, binary
, bytestring
, base16-bytestring >= 1.0
, canonical-json
, cardano-api
, cardano-binary
, cardano-git-rev
, cardano-crypto
, cardano-crypto-class ^>= 2.0
, cardano-crypto-wrapper ^>= 1.4
, cardano-data ^>= 0.1
, cardano-git-rev
, cardano-ledger-alonzo ^>= 0.1
, cardano-ledger-byron ^>= 0.1
, cardano-ledger-conway
, cardano-ledger-core ^>= 0.1
, cardano-ledger-shelley ^>= 0.1
, cardano-ledger-shelley-ma ^>= 0.1
, cardano-ping
, cardano-prelude
, cardano-protocol-tpraos ^>= 0.1
, cardano-slotting ^>= 0.1
, vector-map ^>= 0.1
, contra-tracer
, cardano-strict-containers ^>= 0.1
, cborg >= 0.2.4 && < 0.3
, containers
, contra-tracer
, cryptonite
, deepseq
, directory
, filepath
, formatting
, io-classes
, iproute
, mtl
, network
Expand All @@ -144,17 +149,17 @@ library
, prettyprinter
, prettyprinter-ansi-terminal
, random
, cardano-ledger-shelley ^>= 0.1
, set-algebra ^>= 0.1
, split
, cardano-strict-containers ^>= 0.1
, strict-stm
, text
, time
, transformers
, transformers-except ^>= 0.1.3
, unliftio-core
, utf8-string
, vector
, vector-map ^>= 0.1
, yaml

executable cardano-cli
Expand All @@ -177,9 +182,9 @@ test-suite cardano-cli-test
type: exitcode-stdio-1.0

build-depends: aeson
, bech32 >= 1.1.0
, base16-bytestring
, bytestring
, bech32 >= 1.1.0
, cardano-api
, cardano-api:gen
, cardano-cli
Expand Down
5 changes: 5 additions & 0 deletions cardano-cli/src/Cardano/CLI/Parsers.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module Cardano.CLI.Parsers
) where

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)
Expand Down Expand Up @@ -50,6 +51,7 @@ parseClientCommand =
-- so we list it first.
[ parseShelley
, parseByron
, parsePing
, parseDeprecatedShelleySubcommand
, backwardsCompatibilityCommands
, parseDisplayVersion opts
Expand All @@ -67,6 +69,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
181 changes: 181 additions & 0 deletions cardano-cli/src/Cardano/CLI/Ping.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeApplications #-}

{- HLINT "Avoid $" -}

module Cardano.CLI.Ping
( PingCmd(..)
, PingClientCmdError(..)
, renderPingClientCmdError
, runPingCmd
, parsePingCmd
) where

import Control.Applicative (optional)
import Control.Concurrent.Class.MonadSTM.Strict (StrictTMVar)
import qualified Control.Concurrent.Class.MonadSTM.Strict as STM
import Control.Exception (SomeException)
import Control.Monad (forM, unless, when)
import Control.Monad.Class.MonadAsync (MonadAsync (async, wait, waitCatch))
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Except (ExceptT)
import Control.Monad.Trans.Except.Extra (left)
import Control.Tracer (Tracer (..))
import Data.List (foldl')
import qualified Data.List as L
import Data.Maybe (isNothing)
import Data.Text (Text)
import qualified Data.Text as T
import Data.Word (Word32)
import Network.Socket (AddrInfo)
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

import qualified Cardano.Network.Ping as CNP

data PingClientCmdError
= PingClientCmdErrorOfInvalidHostIp
| PingClientCmdErrorOfExceptions ![(AddrInfo, SomeException)]


data PingCmd = PingCmd
{ pingCmdCount :: !Word32
, pingCmdHost :: !(Maybe String)
, pingCmdUnixSock :: !(Maybe String)
, pingCmdPort :: !String
, pingCmdMagic :: !Word32
, pingCmdJson :: !Bool
, pingCmdQuiet :: !Bool
} deriving (Eq, Show)

pingClient :: Tracer IO CNP.LogMsg -> Tracer IO String -> PingCmd -> [CNP.NodeVersion] -> AddrInfo -> IO ()
pingClient stdout stderr cmd = CNP.pingClient stdout stderr opts
where opts = CNP.PingOpts
{ CNP.pingOptsQuiet = pingCmdQuiet cmd
, CNP.pingOptsJson = pingCmdJson cmd
, CNP.pingOptsCount = pingCmdCount cmd
, CNP.pingOptsHost = pingCmdHost cmd
, CNP.pingOptsUnixSock = pingCmdUnixSock cmd
, CNP.pingOptsPort = pingCmdPort cmd
, CNP.pingOptsMagic = pingCmdMagic cmd
}

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

msgQueue <- liftIO STM.newEmptyTMVarIO

when (isNothing (pingCmdHost options) && isNothing (pingCmdUnixSock options)) $
left PingClientCmdErrorOfInvalidHostIp

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

laid <- liftIO . async $ CNP.logger msgQueue $ pingCmdJson options
caids <- forM addresses $ liftIO . async . pingClient (Tracer $ doLog msgQueue) (Tracer doErrLog) options versions
res <- L.zip addresses <$> mapM (liftIO . waitCatch) caids
liftIO $ doLog msgQueue CNP.LogEnd
liftIO $ wait laid
case foldl' partition ([],[]) res of
([], _) -> liftIO IO.exitSuccess
(es, []) -> left $ PingClientCmdErrorOfExceptions es
(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 CNP.LogMsg -> CNP.LogMsg -> IO ()
doLog msgQueue msg = STM.atomically $ STM.putTMVar msgQueue msg

doErrLog :: String -> IO ()
doErrLog = IO.hPutStrLn IO.stderr

renderPingClientCmdError :: PingClientCmdError -> Text
renderPingClientCmdError = \case
PingClientCmdErrorOfInvalidHostIp -> "Specify host/ip with '-h <hostname>' or a unix socket with -u <file name>"
PingClientCmdErrorOfExceptions es -> T.intercalate "\n" $ T.pack . show <$> es

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 keeps sending keep alive messages."
]
]

pPing :: Opt.Parser PingCmd
pPing = PingCmd
<$> ( Opt.option Opt.auto $ mconcat
[ Opt.long "count"
, Opt.short 'c'
, Opt.metavar "COUNT"
, Opt.help $ mconcat
[ "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.strOption $ mconcat
[ Opt.long "host"
, Opt.short 'h'
, Opt.metavar "HOST"
, Opt.help "Hostname/IP, e.g. relay.iohk.example."
]
)
<*> ( optional $ Opt.strOption $ mconcat
[ Opt.long "unixsock"
, Opt.short 'u'
, Opt.metavar "SOCKET"
, Opt.help "Unix socket, e.g. file.socket."
]
)
<*> ( Opt.strOption $ mconcat
[ Opt.long "port"
, Opt.short 'p'
, Opt.metavar "PORT"
, Opt.help "Port number, e.g. 1234."
, Opt.value "3001"
]
)
<*> ( Opt.option Opt.auto $ mconcat
[ Opt.long "magic"
, Opt.short 'm'
, Opt.metavar "MAGIC"
, Opt.help "Network magic."
, Opt.value CNP.mainnetMagic
]
)
<*> ( Opt.switch $ mconcat
[ Opt.long "json"
, Opt.short 'j'
, Opt.help "JSON output flag."
]
)
<*> ( Opt.switch $ mconcat
[ Opt.long "quiet"
, Opt.short 'q'
, Opt.help "Quiet flag, CSV/JSON only output"
]
)
7 changes: 7 additions & 0 deletions cardano-cli/src/Cardano/CLI/Run.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import qualified System.IO as IO
import Cardano.CLI.Byron.Commands (ByronCommand)
import Cardano.CLI.Byron.Run (ByronClientCmdError, renderByronClientCmdError,
runByronClientCommand)
import Cardano.CLI.Ping (PingCmd (..), PingClientCmdError (..), renderPingClientCmdError, runPingCmd)
import Cardano.CLI.Shelley.Commands (ShelleyCommand)
import Cardano.CLI.Shelley.Run (ShelleyClientCmdError, renderShelleyClientCmdError,
runShelleyClientCommand)
Expand Down Expand Up @@ -48,16 +49,20 @@ data ClientCommand =
-- now-deprecated \"shelley\" subcommand.
| DeprecatedShelleySubcommand ShelleyCommand

| CliPingCommand PingCmd

| forall a. Help ParserPrefs (ParserInfo a)
| DisplayVersion

data ClientCommandErrors
= ByronClientError ByronClientCmdError
| ShelleyClientError ShelleyCommand ShelleyClientCmdError
| PingClientError PingClientCmdError

runClientCommand :: ClientCommand -> ExceptT ClientCommandErrors IO ()
runClientCommand (ByronCommand c) = firstExceptT ByronClientError $ runByronClientCommand c
runClientCommand (ShelleyCommand c) = firstExceptT (ShelleyClientError c) $ runShelleyClientCommand c
runClientCommand (CliPingCommand c) = firstExceptT PingClientError $ runPingCmd c
runClientCommand (DeprecatedShelleySubcommand c) =
firstExceptT (ShelleyClientError c)
$ runShelleyClientCommandWithDeprecationWarning
Expand All @@ -70,6 +75,8 @@ renderClientCommandError (ByronClientError err) =
renderByronClientCmdError err
renderClientCommandError (ShelleyClientError cmd err) =
renderShelleyClientCmdError cmd err
renderClientCommandError (PingClientError err) =
renderPingClientCmdError err

-- | Combine an 'ExceptT' that will write a warning message to @stderr@ with
-- the provided 'ExceptT'.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module Cardano.Node.Tracing.Tracers.NodeToNode
) where

import Cardano.Logging
import Data.Aeson (Value (String), ToJSON (..), (.=))
import Data.Aeson (ToJSON (..), Value (String), (.=))
import Data.Proxy (Proxy (..))
import Data.Text (pack)
import Network.TypedProtocol.Codec (AnyMessageAndAgency (..))
Expand Down

0 comments on commit 04ea9ee

Please sign in to comment.