Skip to content

Commit a2fda17

Browse files
authored
Merge pull request #4664 from input-output-hk/newhoggy/cardano-ping
New cardano-cli ping command.
2 parents 3888d54 + f270530 commit a2fda17

File tree

7 files changed

+227
-12
lines changed

7 files changed

+227
-12
lines changed

cabal.project

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ index-state: 2023-03-06T05:24:58Z
1616

1717
index-state:
1818
, hackage.haskell.org 2023-03-06T05:24:58Z
19-
, cardano-haskell-packages 2023-02-28T09:20:07Z
19+
, cardano-haskell-packages 2023-03-21T10:00:52Z
2020

2121
packages:
2222
cardano-api

cardano-cli/cardano-cli.cabal

+12-7
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ library
9191
Cardano.CLI.Shelley.Run.Read
9292
Cardano.CLI.Shelley.Run.Validate
9393

94+
Cardano.CLI.Ping
95+
9496
Cardano.CLI.TopHandler
9597

9698
other-modules: Paths_cardano_cli
@@ -100,35 +102,38 @@ library
100102
, aeson-pretty >= 0.8.5
101103
, ansi-terminal
102104
, attoparsec
105+
, base16-bytestring >= 1.0
103106
, bech32 >= 1.1.0
104107
, binary
105108
, bytestring
106-
, base16-bytestring >= 1.0
107109
, canonical-json
108110
, cardano-api
109111
, cardano-binary
110-
, cardano-git-rev
111112
, cardano-crypto
112113
, cardano-crypto-class ^>= 2.0
113114
, cardano-crypto-wrapper ^>= 1.4
114115
, cardano-data ^>= 0.1
116+
, cardano-git-rev
115117
, cardano-ledger-alonzo ^>= 0.1
116118
, cardano-ledger-byron ^>= 0.1
117119
, cardano-ledger-conway
118120
, cardano-ledger-core ^>= 0.1
121+
, cardano-ledger-shelley ^>= 0.1
119122
, cardano-ledger-shelley-ma ^>= 0.1
123+
, cardano-ping
120124
, cardano-prelude
121125
, cardano-protocol-tpraos ^>= 0.1
122126
, cardano-slotting ^>= 0.1
123-
, vector-map ^>= 0.1
124-
, contra-tracer
127+
, cardano-strict-containers ^>= 0.1
125128
, cborg >= 0.2.4 && < 0.3
126129
, containers
130+
, contra-tracer
127131
, cryptonite
128132
, deepseq
129133
, directory
130134
, filepath
131135
, formatting
136+
, io-classes
132137
, iproute
133138
, mtl
134139
, network
@@ -144,17 +149,17 @@ library
144149
, prettyprinter
145150
, prettyprinter-ansi-terminal
146151
, random
147-
, cardano-ledger-shelley ^>= 0.1
148152
, set-algebra ^>= 0.1
149153
, split
150-
, cardano-strict-containers ^>= 0.1
154+
, strict-stm
151155
, text
152156
, time
153157
, transformers
154158
, transformers-except ^>= 0.1.3
155159
, unliftio-core
156160
, utf8-string
157161
, vector
162+
, vector-map ^>= 0.1
158163
, yaml
159164

160165
executable cardano-cli
@@ -177,9 +182,9 @@ test-suite cardano-cli-test
177182
type: exitcode-stdio-1.0
178183

179184
build-depends: aeson
180-
, bech32 >= 1.1.0
181185
, base16-bytestring
182186
, bytestring
187+
, bech32 >= 1.1.0
183188
, cardano-api
184189
, cardano-api:gen
185190
, cardano-cli

cardano-cli/src/Cardano/CLI/Parsers.hs

+5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module Cardano.CLI.Parsers
99
) where
1010

1111
import Cardano.CLI.Byron.Parsers (backwardsCompatibilityCommands, parseByronCommands)
12+
import Cardano.CLI.Ping (parsePingCmd)
1213
import Cardano.CLI.Render (customRenderHelp)
1314
import Cardano.CLI.Run (ClientCommand (..))
1415
import Cardano.CLI.Shelley.Parsers (parseShelleyCommands)
@@ -50,6 +51,7 @@ parseClientCommand =
5051
-- so we list it first.
5152
[ parseShelley
5253
, parseByron
54+
, parsePing
5355
, parseDeprecatedShelleySubcommand
5456
, backwardsCompatibilityCommands
5557
, parseDisplayVersion opts
@@ -67,6 +69,9 @@ parseByron =
6769
parseByronCommands
6870
]
6971

72+
parsePing :: Parser ClientCommand
73+
parsePing = CliPingCommand <$> parsePingCmd
74+
7075
-- | Parse Shelley-related commands at the top level of the CLI.
7176
parseShelley :: Parser ClientCommand
7277
parseShelley = ShelleyCommand <$> parseShelleyCommands

cardano-cli/src/Cardano/CLI/Ping.hs

+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
{-# LANGUAGE LambdaCase #-}
2+
{-# LANGUAGE TypeApplications #-}
3+
4+
{- HLINT ignore "Move brackets to avoid $" -}
5+
6+
module Cardano.CLI.Ping
7+
( PingCmd(..)
8+
, PingClientCmdError(..)
9+
, renderPingClientCmdError
10+
, runPingCmd
11+
, parsePingCmd
12+
) where
13+
14+
import Control.Applicative ((<|>))
15+
import Control.Concurrent.Class.MonadSTM.Strict (StrictTMVar)
16+
import qualified Control.Concurrent.Class.MonadSTM.Strict as STM
17+
import Control.Exception (SomeException)
18+
import Control.Monad (forM, unless)
19+
import Control.Monad.Class.MonadAsync (MonadAsync (async, wait, waitCatch))
20+
import Control.Monad.IO.Class (liftIO)
21+
import Control.Monad.Trans.Except (ExceptT)
22+
import Control.Monad.Trans.Except.Extra (left)
23+
import Control.Tracer (Tracer (..))
24+
import Data.List (foldl')
25+
import qualified Data.List as L
26+
import Data.Text (Text)
27+
import qualified Data.Text as T
28+
import Data.Word (Word32)
29+
import Network.Socket (AddrInfo)
30+
import qualified Network.Socket as Socket
31+
import qualified Options.Applicative as Opt
32+
import qualified Prettyprinter as PP
33+
import qualified System.Exit as IO
34+
import qualified System.IO as IO
35+
36+
import qualified Cardano.Network.Ping as CNP
37+
38+
newtype PingClientCmdError = PingClientCmdError [(AddrInfo, SomeException)]
39+
40+
data EndPoint = HostEndPoint String | UnixSockEndPoint String deriving (Eq, Show)
41+
42+
maybeHostEndPoint :: EndPoint -> Maybe String
43+
maybeHostEndPoint = \case
44+
HostEndPoint host -> Just host
45+
UnixSockEndPoint _ -> Nothing
46+
47+
maybeUnixSockEndPoint :: EndPoint -> Maybe String
48+
maybeUnixSockEndPoint = \case
49+
HostEndPoint _ -> Nothing
50+
UnixSockEndPoint sock -> Just sock
51+
52+
data PingCmd = PingCmd
53+
{ pingCmdCount :: !Word32
54+
, pingCmdEndPoint :: !EndPoint
55+
, pingCmdPort :: !String
56+
, pingCmdMagic :: !Word32
57+
, pingCmdJson :: !Bool
58+
, pingCmdQuiet :: !Bool
59+
} deriving (Eq, Show)
60+
61+
pingClient :: Tracer IO CNP.LogMsg -> Tracer IO String -> PingCmd -> [CNP.NodeVersion] -> AddrInfo -> IO ()
62+
pingClient stdout stderr cmd = CNP.pingClient stdout stderr opts
63+
where opts = CNP.PingOpts
64+
{ CNP.pingOptsQuiet = pingCmdQuiet cmd
65+
, CNP.pingOptsJson = pingCmdJson cmd
66+
, CNP.pingOptsCount = pingCmdCount cmd
67+
, CNP.pingOptsHost = maybeHostEndPoint (pingCmdEndPoint cmd)
68+
, CNP.pingOptsUnixSock = maybeUnixSockEndPoint (pingCmdEndPoint cmd)
69+
, CNP.pingOptsPort = pingCmdPort cmd
70+
, CNP.pingOptsMagic = pingCmdMagic cmd
71+
}
72+
73+
runPingCmd :: PingCmd -> ExceptT PingClientCmdError IO ()
74+
runPingCmd options = do
75+
let hints = Socket.defaultHints { Socket.addrSocketType = Socket.Stream }
76+
77+
msgQueue <- liftIO STM.newEmptyTMVarIO
78+
79+
-- 'addresses' are all the endpoints to connect to and 'versions' are the node protocol versions
80+
-- to ping with.
81+
(addresses, versions) <- case pingCmdEndPoint options of
82+
HostEndPoint host -> do
83+
addrs <- liftIO $ Socket.getAddrInfo (Just hints) (Just host) (Just (pingCmdPort options))
84+
return (addrs, CNP.supportedNodeToNodeVersions $ pingCmdMagic options)
85+
UnixSockEndPoint fname -> do
86+
let addr = Socket.AddrInfo
87+
[] Socket.AF_UNIX Socket.Stream
88+
Socket.defaultProtocol (Socket.SockAddrUnix fname) Nothing
89+
return ([addr], CNP.supportedNodeToClientVersions $ pingCmdMagic options)
90+
91+
-- Logger async thread handle
92+
laid <- liftIO . async $ CNP.logger msgQueue $ pingCmdJson options
93+
-- Ping client thread handles
94+
caids <- forM addresses $ liftIO . async . pingClient (Tracer $ doLog msgQueue) (Tracer doErrLog) options versions
95+
res <- L.zip addresses <$> mapM (liftIO . waitCatch) caids
96+
liftIO $ doLog msgQueue CNP.LogEnd
97+
liftIO $ wait laid
98+
99+
-- Collect errors 'es' from failed pings and 'addrs' from successful pings.
100+
let (es, addrs) = foldl' partition ([],[]) res
101+
102+
-- Report any errors
103+
case (es, addrs) of
104+
([], _) -> liftIO IO.exitSuccess
105+
(_, []) -> left $ PingClientCmdError es
106+
(_, _) -> do
107+
unless (pingCmdQuiet options) $ mapM_ (liftIO . IO.hPrint IO.stderr) es
108+
liftIO IO.exitSuccess
109+
110+
where
111+
partition :: ([(AddrInfo, SomeException)], [AddrInfo])
112+
-> (AddrInfo, Either SomeException ())
113+
-> ([(AddrInfo, SomeException)], [AddrInfo])
114+
partition (es, as) (a, Left e) = ((a, e) : es, as)
115+
partition (es, as) (a, Right _) = (es, a : as)
116+
117+
doLog :: StrictTMVar IO CNP.LogMsg -> CNP.LogMsg -> IO ()
118+
doLog msgQueue msg = STM.atomically $ STM.putTMVar msgQueue msg
119+
120+
doErrLog :: String -> IO ()
121+
doErrLog = IO.hPutStrLn IO.stderr
122+
123+
renderPingClientCmdError :: PingClientCmdError -> Text
124+
renderPingClientCmdError = \case
125+
PingClientCmdError es -> T.intercalate "\n" $ T.pack . show <$> es
126+
127+
parsePingCmd :: Opt.Parser PingCmd
128+
parsePingCmd = Opt.hsubparser $ mconcat
129+
[ Opt.metavar "ping"
130+
, Opt.command "ping" $ Opt.info pPing $ Opt.progDescDoc $ Just $ mconcat
131+
[ PP.pretty @String "Ping a cardano node either using node-to-node or node-to-client protocol. "
132+
, PP.pretty @String "It negotiates a handshake and keeps sending keep alive messages."
133+
]
134+
]
135+
136+
pHost :: Opt.Parser String
137+
pHost =
138+
Opt.strOption $ mconcat
139+
[ Opt.long "host"
140+
, Opt.short 'h'
141+
, Opt.metavar "HOST"
142+
, Opt.help "Hostname/IP, e.g. relay.iohk.example."
143+
]
144+
145+
pUnixSocket :: Opt.Parser String
146+
pUnixSocket =
147+
Opt.strOption $ mconcat
148+
[ Opt.long "unixsock"
149+
, Opt.short 'u'
150+
, Opt.metavar "SOCKET"
151+
, Opt.help "Unix socket, e.g. file.socket."
152+
]
153+
154+
pEndPoint :: Opt.Parser EndPoint
155+
pEndPoint = fmap HostEndPoint pHost <|> fmap UnixSockEndPoint pUnixSocket
156+
157+
pPing :: Opt.Parser PingCmd
158+
pPing = PingCmd
159+
<$> ( Opt.option Opt.auto $ mconcat
160+
[ Opt.long "count"
161+
, Opt.short 'c'
162+
, Opt.metavar "COUNT"
163+
, Opt.help $ mconcat
164+
[ "Stop after sending count requests and receiving count responses. "
165+
, "If this option is not specified, ping will operate until interrupted. "
166+
]
167+
, Opt.value maxBound
168+
]
169+
)
170+
<*> pEndPoint
171+
<*> ( Opt.strOption $ mconcat
172+
[ Opt.long "port"
173+
, Opt.short 'p'
174+
, Opt.metavar "PORT"
175+
, Opt.help "Port number, e.g. 1234."
176+
, Opt.value "3001"
177+
]
178+
)
179+
<*> ( Opt.option Opt.auto $ mconcat
180+
[ Opt.long "magic"
181+
, Opt.short 'm'
182+
, Opt.metavar "MAGIC"
183+
, Opt.help "Network magic."
184+
, Opt.value CNP.mainnetMagic
185+
]
186+
)
187+
<*> ( Opt.switch $ mconcat
188+
[ Opt.long "json"
189+
, Opt.short 'j'
190+
, Opt.help "JSON output flag."
191+
]
192+
)
193+
<*> ( Opt.switch $ mconcat
194+
[ Opt.long "quiet"
195+
, Opt.short 'q'
196+
, Opt.help "Quiet flag, CSV/JSON only output"
197+
]
198+
)

cardano-cli/src/Cardano/CLI/Run.hs

+7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import qualified System.IO as IO
2121
import Cardano.CLI.Byron.Commands (ByronCommand)
2222
import Cardano.CLI.Byron.Run (ByronClientCmdError, renderByronClientCmdError,
2323
runByronClientCommand)
24+
import Cardano.CLI.Ping (PingCmd (..), PingClientCmdError (..), renderPingClientCmdError, runPingCmd)
2425
import Cardano.CLI.Shelley.Commands (ShelleyCommand)
2526
import Cardano.CLI.Shelley.Run (ShelleyClientCmdError, renderShelleyClientCmdError,
2627
runShelleyClientCommand)
@@ -48,16 +49,20 @@ data ClientCommand =
4849
-- now-deprecated \"shelley\" subcommand.
4950
| DeprecatedShelleySubcommand ShelleyCommand
5051

52+
| CliPingCommand PingCmd
53+
5154
| forall a. Help ParserPrefs (ParserInfo a)
5255
| DisplayVersion
5356

5457
data ClientCommandErrors
5558
= ByronClientError ByronClientCmdError
5659
| ShelleyClientError ShelleyCommand ShelleyClientCmdError
60+
| PingClientError PingClientCmdError
5761

5862
runClientCommand :: ClientCommand -> ExceptT ClientCommandErrors IO ()
5963
runClientCommand (ByronCommand c) = firstExceptT ByronClientError $ runByronClientCommand c
6064
runClientCommand (ShelleyCommand c) = firstExceptT (ShelleyClientError c) $ runShelleyClientCommand c
65+
runClientCommand (CliPingCommand c) = firstExceptT PingClientError $ runPingCmd c
6166
runClientCommand (DeprecatedShelleySubcommand c) =
6267
firstExceptT (ShelleyClientError c)
6368
$ runShelleyClientCommandWithDeprecationWarning
@@ -70,6 +75,8 @@ renderClientCommandError (ByronClientError err) =
7075
renderByronClientCmdError err
7176
renderClientCommandError (ShelleyClientError cmd err) =
7277
renderShelleyClientCmdError cmd err
78+
renderClientCommandError (PingClientError err) =
79+
renderPingClientCmdError err
7380

7481
-- | Combine an 'ExceptT' that will write a warning message to @stderr@ with
7582
-- the provided 'ExceptT'.

cardano-node/src/Cardano/Node/Tracing/Tracers/NodeToNode.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ module Cardano.Node.Tracing.Tracers.NodeToNode
1313
) where
1414

1515
import Cardano.Logging
16-
import Data.Aeson (Value (String), ToJSON (..), (.=))
16+
import Data.Aeson (ToJSON (..), Value (String), (.=))
1717
import Data.Proxy (Proxy (..))
1818
import Data.Text (pack)
1919
import Network.TypedProtocol.Codec (AnyMessageAndAgency (..))

flake.lock

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)