Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New cardano-cli ping command. #4664

Merged
merged 3 commits into from
Mar 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ index-state: 2023-03-06T05:24:58Z

index-state:
, hackage.haskell.org 2023-03-06T05:24:58Z
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In future lets do index state bumps in separate PRs so if it happens to break CI/Nix infrastructure we can deal with it separately.

, cardano-haskell-packages 2023-02-28T09:20:07Z
, cardano-haskell-packages 2023-03-21T10:00:52Z

packages:
cardano-api
Expand Down
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

, 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
198 changes: 198 additions & 0 deletions cardano-cli/src/Cardano/CLI/Ping.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeApplications #-}

{- HLINT ignore "Move brackets to avoid $" -}

module Cardano.CLI.Ping
Jimbo4350 marked this conversation as resolved.
Show resolved Hide resolved
( PingCmd(..)
, PingClientCmdError(..)
, renderPingClientCmdError
, runPingCmd
, parsePingCmd
) where

import Control.Applicative ((<|>))
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)
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.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

newtype PingClientCmdError = PingClientCmdError [(AddrInfo, SomeException)]

data EndPoint = HostEndPoint String | UnixSockEndPoint String deriving (Eq, Show)

maybeHostEndPoint :: EndPoint -> Maybe String
maybeHostEndPoint = \case
HostEndPoint host -> Just host
UnixSockEndPoint _ -> Nothing

maybeUnixSockEndPoint :: EndPoint -> Maybe String
maybeUnixSockEndPoint = \case
HostEndPoint _ -> Nothing
UnixSockEndPoint sock -> Just sock

data PingCmd = PingCmd
{ pingCmdCount :: !Word32
, pingCmdEndPoint :: !EndPoint
, 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 = maybeHostEndPoint (pingCmdEndPoint cmd)
, CNP.pingOptsUnixSock = maybeUnixSockEndPoint (pingCmdEndPoint cmd)
, CNP.pingOptsPort = pingCmdPort cmd
, CNP.pingOptsMagic = pingCmdMagic cmd
}

runPingCmd :: PingCmd -> ExceptT PingClientCmdError IO ()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add some comments to this code to make it clearer as to what is happening?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments added

runPingCmd options = do
let hints = Socket.defaultHints { Socket.addrSocketType = Socket.Stream }

msgQueue <- liftIO STM.newEmptyTMVarIO

-- 'addresses' are all the endpoints to connect to and 'versions' are the node protocol versions
-- to ping with.
(addresses, versions) <- case pingCmdEndPoint options of
HostEndPoint host -> do
addrs <- liftIO $ Socket.getAddrInfo (Just hints) (Just host) (Just (pingCmdPort options))
return (addrs, CNP.supportedNodeToNodeVersions $ pingCmdMagic options)
UnixSockEndPoint fname -> do
let addr = Socket.AddrInfo
[] Socket.AF_UNIX Socket.Stream
Socket.defaultProtocol (Socket.SockAddrUnix fname) Nothing
return ([addr], CNP.supportedNodeToClientVersions $ pingCmdMagic options)

-- Logger async thread handle
laid <- liftIO . async $ CNP.logger msgQueue $ pingCmdJson options
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments here would be useful

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

-- Ping client thread handles
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LogEnd is signalling to stop logging right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. It logs JSON so the LogEnd is required to close it off with ] }.

liftIO $ wait laid

-- Collect errors 'es' from failed pings and 'addrs' from successful pings.
let (es, addrs) = foldl' partition ([],[]) res

-- Report any errors
case (es, addrs) of
([], _) -> liftIO IO.exitSuccess
(_, []) -> left $ PingClientCmdError es
(_, _) -> do
unless (pingCmdQuiet options) $ mapM_ (liftIO . IO.hPrint IO.stderr) es
liftIO IO.exitSuccess

where
partition :: ([(AddrInfo, SomeException)], [AddrInfo])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments here would be useful

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the comments in the function body suffice?

-> (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
PingClientCmdError 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."
]
]

pHost :: Opt.Parser String
pHost =
Opt.strOption $ mconcat
[ Opt.long "host"
, Opt.short 'h'
, Opt.metavar "HOST"
, Opt.help "Hostname/IP, e.g. relay.iohk.example."
]

pUnixSocket :: Opt.Parser String
pUnixSocket =
Opt.strOption $ mconcat
[ Opt.long "unixsock"
, Opt.short 'u'
, Opt.metavar "SOCKET"
, Opt.help "Unix socket, e.g. file.socket."
]

pEndPoint :: Opt.Parser EndPoint
pEndPoint = fmap HostEndPoint pHost <|> fmap UnixSockEndPoint pUnixSocket

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
]
)
<*> pEndPoint
<*> ( 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
Jimbo4350 marked this conversation as resolved.
Show resolved Hide resolved
| 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
6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.