Skip to content

Commit 78e16ab

Browse files
committed
New cardano-cli ping command.
1 parent 15556f3 commit 78e16ab

File tree

6 files changed

+649
-2
lines changed

6 files changed

+649
-2
lines changed

cardano-api/src/Cardano/Api/StakePoolMetadata.hs

-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ import Cardano.Ledger.Crypto (StandardCrypto)
4242

4343
import qualified Cardano.Ledger.Keys as Shelley
4444

45-
4645
-- ----------------------------------------------------------------------------
4746
-- Stake pool metadata
4847
--

cardano-cli/cardano-cli.cabal

+7
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ library
9090
Cardano.CLI.Shelley.Run.Read
9191
Cardano.CLI.Shelley.Run.Validate
9292

93+
Cardano.CLI.Ping
94+
Cardano.CLI.Ping.Lib
95+
9396
Cardano.CLI.TopHandler
9497

9598
other-modules: Paths_cardano_cli
@@ -125,8 +128,10 @@ library
125128
, directory
126129
, filepath
127130
, formatting
131+
, io-classes
128132
, iproute
129133
, network
134+
, network-mux
130135
, optparse-applicative-fork
131136
, ouroboros-consensus
132137
, ouroboros-consensus-byron
@@ -141,7 +146,9 @@ library
141146
, set-algebra
142147
, split
143148
, strict-containers
149+
, strict-stm
144150
, text
151+
, tdigest
145152
, time
146153
, transformers
147154
, transformers-except

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
{-# LANGUAGE FlexibleContexts #-}
22
{-# LANGUAGE OverloadedStrings #-}
3+
{-# LANGUAGE TypeApplications #-}
34

45
module Cardano.CLI.Parsers
56
( opts
67
, pref
78
) where
89

9-
import Cardano.Prelude
1010
import Cardano.CLI.Byron.Parsers (backwardsCompatibilityCommands, parseByronCommands)
11+
import Cardano.CLI.Ping (parsePingCmd)
1112
import Cardano.CLI.Render (customRenderHelp)
1213
import Cardano.CLI.Run (ClientCommand (..))
1314
import Cardano.CLI.Shelley.Parsers (parseShelleyCommands)
15+
import Cardano.Prelude
1416
import Options.Applicative
1517
import Prelude (String)
1618

@@ -45,6 +47,7 @@ parseClientCommand =
4547
-- so we list it first.
4648
[ parseShelley
4749
, parseByron
50+
, parsePing
4851
, parseDeprecatedShelleySubcommand
4952
, backwardsCompatibilityCommands
5053
, parseDisplayVersion opts
@@ -62,6 +65,9 @@ parseByron =
6265
parseByronCommands
6366
]
6467

68+
parsePing :: Parser ClientCommand
69+
parsePing = CliPingCommand <$> parsePingCmd
70+
6571
-- | Parse Shelley-related commands at the top level of the CLI.
6672
parseShelley :: Parser ClientCommand
6773
parseShelley = ShelleyCommand <$> parseShelleyCommands

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

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
{-# LANGUAGE BangPatterns #-}
2+
{-# LANGUAGE DataKinds #-}
3+
{-# LANGUAGE InstanceSigs #-}
4+
{-# LANGUAGE LambdaCase #-}
5+
{-# LANGUAGE NamedFieldPuns #-}
6+
{-# LANGUAGE RankNTypes #-}
7+
{-# LANGUAGE RecordWildCards #-}
8+
{-# LANGUAGE TypeApplications #-}
9+
10+
module Cardano.CLI.Ping
11+
( PingCmd(..)
12+
, PingCmdError(..)
13+
, renderPingCmdError
14+
, runPingCmd
15+
, parsePingCmd
16+
) where
17+
18+
import Cardano.CLI.Ping.Lib
19+
20+
import Control.Applicative (Applicative (..), optional)
21+
import Control.Exception (SomeException)
22+
import Control.Monad (Monad (..), forM, mapM, mapM_, unless, when)
23+
import Control.Monad.Class.MonadAsync (MonadAsync (async, wait, waitCatch))
24+
import Control.Monad.Class.MonadSTM.Strict
25+
import Control.Monad.IO.Class (liftIO)
26+
import Control.Monad.Trans.Except (ExceptT, throwE)
27+
import Control.Tracer (Tracer (..))
28+
import Data.Bool ((&&))
29+
import Data.Either (Either (..))
30+
import Data.Function (($), (.))
31+
import Data.Functor ((<$>))
32+
import Data.List (foldl')
33+
import Data.Maybe (Maybe (..), isNothing)
34+
import Data.Monoid (mconcat)
35+
import Data.Semigroup ((<>))
36+
import Data.String (String)
37+
import Data.Text (Text)
38+
import GHC.Enum (Bounded (..))
39+
import Network.Socket (AddrInfo)
40+
import System.IO (IO)
41+
import Text.Show (Show(..))
42+
43+
import qualified Data.List as L
44+
import qualified Data.Text as T
45+
import qualified Network.Socket as Socket
46+
import qualified Options.Applicative as Opt
47+
import qualified Prettyprinter as PP
48+
import qualified System.Exit as IO
49+
import qualified System.IO as IO
50+
51+
data PingCmdError
52+
= PingCmdErrorOfInvalidHostIp
53+
| PingCmdErrorOfExceptions ![(AddrInfo, SomeException)]
54+
55+
runPingCmd :: PingCmd -> ExceptT PingCmdError IO ()
56+
runPingCmd options = do
57+
let hints = Socket.defaultHints { Socket.addrSocketType = Socket.Stream }
58+
59+
msgQueue <- liftIO newEmptyTMVarIO
60+
61+
when (isNothing (pingCmdHost options) && isNothing (pingCmdUnixSock options)) $
62+
throwE PingCmdErrorOfInvalidHostIp
63+
64+
(addresses, versions) <- case pingCmdUnixSock options of
65+
Nothing -> do
66+
addrs <- liftIO $ Socket.getAddrInfo (Just hints) (pingCmdHost options) (Just (pingCmdPort options))
67+
return (addrs, supportedNodeToNodeVersions $ pingCmdMagic options)
68+
Just fname ->
69+
return
70+
( [ Socket.AddrInfo [] Socket.AF_UNIX Socket.Stream
71+
Socket.defaultProtocol (Socket.SockAddrUnix fname)
72+
Nothing
73+
]
74+
, supportedNodeToClientVersions $ pingCmdMagic options
75+
)
76+
77+
laid <- liftIO . async $ logger msgQueue $ pingCmdJson options
78+
caids <- forM addresses $ liftIO . async . pingClient (Tracer $ doLog msgQueue) options versions
79+
res <- L.zip addresses <$> mapM (liftIO . waitCatch) caids
80+
liftIO $ doLog msgQueue LogEnd
81+
liftIO $ wait laid
82+
case foldl' partition ([],[]) res of
83+
([], _) -> liftIO IO.exitSuccess
84+
(es, []) -> throwE $ PingCmdErrorOfExceptions es
85+
(es, _) -> do
86+
unless (pingCmdQuiet options) $ mapM_ (liftIO . IO.hPrint IO.stderr) es
87+
liftIO IO.exitSuccess
88+
89+
where
90+
partition :: ([(AddrInfo, SomeException)], [AddrInfo])
91+
-> (AddrInfo, Either SomeException ())
92+
-> ([(AddrInfo, SomeException)], [AddrInfo])
93+
partition (es, as) (a, Left e) = ((a, e) : es, as)
94+
partition (es, as) (a, Right _) = (es, a : as)
95+
96+
doLog :: StrictTMVar IO LogMsg -> LogMsg -> IO ()
97+
doLog msgQueue msg = atomically $ putTMVar msgQueue msg
98+
99+
renderPingCmdError :: PingCmdError -> Text
100+
renderPingCmdError = \case
101+
PingCmdErrorOfInvalidHostIp -> "Specify host/ip with '-h <hostname>' or a unix socket with -u <file name>"
102+
PingCmdErrorOfExceptions es -> T.intercalate "\n" $ T.pack . show <$> es
103+
104+
parsePingCmd :: Opt.Parser PingCmd
105+
parsePingCmd = Opt.hsubparser $ mconcat
106+
[ Opt.metavar "ping"
107+
, Opt.command "ping" $ Opt.info pPing $ Opt.progDescDoc $ Just $ mconcat
108+
[ PP.pretty @String "Ping a cardano node either using node-to-node or node-to-client protocol. "
109+
, PP.pretty @String "It negotiates a handshake and keep sending keep alive messages."
110+
]
111+
]
112+
113+
pPing :: Opt.Parser PingCmd
114+
pPing = PingCmd
115+
<$> Opt.option Opt.auto
116+
( Opt.long "count"
117+
<> Opt.short 'c'
118+
<> Opt.metavar "COUNT"
119+
<> Opt.help
120+
( "Stop after sending count requests and receiving count responses. "
121+
<> "If this option is not specified, ping will operate until interrupted. "
122+
)
123+
<> Opt.value maxBound
124+
)
125+
<*> optional
126+
( Opt.option Opt.auto
127+
( Opt.long "host"
128+
<> Opt.short 'h'
129+
<> Opt.metavar "HOST"
130+
<> Opt.help "Hostname/IP, e.g. relay.iohk.example."
131+
)
132+
)
133+
<*> optional
134+
( Opt.option Opt.auto
135+
( Opt.long "unixsock"
136+
<> Opt.short 'u'
137+
<> Opt.metavar "SOCKET"
138+
<> Opt.help "Unix socket, e.g. file.socket."
139+
)
140+
)
141+
<*> Opt.option Opt.auto
142+
( Opt.long "port"
143+
<> Opt.short 'p'
144+
<> Opt.metavar "PORT"
145+
<> Opt.help "Port number, e.g. 1234."
146+
<> Opt.value "3001"
147+
)
148+
<*> Opt.option Opt.auto
149+
( Opt.long "magic"
150+
<> Opt.short 'm'
151+
<> Opt.metavar "MAGIC"
152+
<> Opt.help "Network magic."
153+
<> Opt.value mainnetMagic
154+
)
155+
<*> Opt.switch
156+
( Opt.long "json"
157+
<> Opt.short 'j'
158+
<> Opt.help "JSON output flag."
159+
)
160+
<*> Opt.switch
161+
( Opt.long "quiet"
162+
<> Opt.short 'q'
163+
<> Opt.help "Quiet flag, CSV/JSON only output"
164+
)

0 commit comments

Comments
 (0)