You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

326 lines
7.6 KiB

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "strconv"
  7. "github.com/spf13/cobra"
  8. "github.com/tendermint/tendermint/libs/log"
  9. e2e "github.com/tendermint/tendermint/test/e2e/pkg"
  10. )
  11. var (
  12. logger = log.MustNewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo, false)
  13. )
  14. func main() {
  15. NewCLI().Run()
  16. }
  17. // CLI is the Cobra-based command-line interface.
  18. type CLI struct {
  19. root *cobra.Command
  20. testnet *e2e.Testnet
  21. preserve bool
  22. }
  23. // NewCLI sets up the CLI.
  24. func NewCLI() *CLI {
  25. cli := &CLI{}
  26. cli.root = &cobra.Command{
  27. Use: "runner",
  28. Short: "End-to-end test runner",
  29. SilenceUsage: true,
  30. SilenceErrors: true, // we'll output them ourselves in Run()
  31. PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
  32. file, err := cmd.Flags().GetString("file")
  33. if err != nil {
  34. return err
  35. }
  36. testnet, err := e2e.LoadTestnet(file)
  37. if err != nil {
  38. return err
  39. }
  40. cli.testnet = testnet
  41. return nil
  42. },
  43. RunE: func(cmd *cobra.Command, args []string) error {
  44. if err := Cleanup(cli.testnet); err != nil {
  45. return err
  46. }
  47. if err := Setup(cli.testnet); err != nil {
  48. return err
  49. }
  50. chLoadResult := make(chan error)
  51. ctx, loadCancel := context.WithCancel(context.Background())
  52. defer loadCancel()
  53. go func() {
  54. err := Load(ctx, cli.testnet, 1)
  55. chLoadResult <- err
  56. }()
  57. if err := Start(cli.testnet); err != nil {
  58. return err
  59. }
  60. if err := Wait(cli.testnet, 5); err != nil { // allow some txs to go through
  61. return err
  62. }
  63. if cli.testnet.HasPerturbations() {
  64. if err := Perturb(cli.testnet); err != nil {
  65. return err
  66. }
  67. if err := Wait(cli.testnet, 5); err != nil { // allow some txs to go through
  68. return err
  69. }
  70. }
  71. if cli.testnet.Evidence > 0 {
  72. if err := InjectEvidence(cli.testnet, cli.testnet.Evidence); err != nil {
  73. return err
  74. }
  75. if err := Wait(cli.testnet, 5); err != nil { // ensure chain progress
  76. return err
  77. }
  78. }
  79. loadCancel()
  80. if err := <-chLoadResult; err != nil {
  81. return fmt.Errorf("transaction load failed: %w", err)
  82. }
  83. if err := Wait(cli.testnet, 5); err != nil { // wait for network to settle before tests
  84. return err
  85. }
  86. if err := Test(cli.testnet); err != nil {
  87. return err
  88. }
  89. if !cli.preserve {
  90. if err := Cleanup(cli.testnet); err != nil {
  91. return err
  92. }
  93. }
  94. return nil
  95. },
  96. }
  97. cli.root.PersistentFlags().StringP("file", "f", "", "Testnet TOML manifest")
  98. _ = cli.root.MarkPersistentFlagRequired("file")
  99. cli.root.Flags().BoolVarP(&cli.preserve, "preserve", "p", false,
  100. "Preserves the running of the test net after tests are completed")
  101. cli.root.SetHelpCommand(&cobra.Command{
  102. Use: "no-help",
  103. Hidden: true,
  104. })
  105. cli.root.AddCommand(&cobra.Command{
  106. Use: "setup",
  107. Short: "Generates the testnet directory and configuration",
  108. RunE: func(cmd *cobra.Command, args []string) error {
  109. return Setup(cli.testnet)
  110. },
  111. })
  112. cli.root.AddCommand(&cobra.Command{
  113. Use: "start",
  114. Short: "Starts the Docker testnet, waiting for nodes to become available",
  115. RunE: func(cmd *cobra.Command, args []string) error {
  116. _, err := os.Stat(cli.testnet.Dir)
  117. if os.IsNotExist(err) {
  118. err = Setup(cli.testnet)
  119. }
  120. if err != nil {
  121. return err
  122. }
  123. return Start(cli.testnet)
  124. },
  125. })
  126. cli.root.AddCommand(&cobra.Command{
  127. Use: "perturb",
  128. Short: "Perturbs the Docker testnet, e.g. by restarting or disconnecting nodes",
  129. RunE: func(cmd *cobra.Command, args []string) error {
  130. return Perturb(cli.testnet)
  131. },
  132. })
  133. cli.root.AddCommand(&cobra.Command{
  134. Use: "wait",
  135. Short: "Waits for a few blocks to be produced and all nodes to catch up",
  136. RunE: func(cmd *cobra.Command, args []string) error {
  137. return Wait(cli.testnet, 5)
  138. },
  139. })
  140. cli.root.AddCommand(&cobra.Command{
  141. Use: "stop",
  142. Short: "Stops the Docker testnet",
  143. RunE: func(cmd *cobra.Command, args []string) error {
  144. logger.Info("Stopping testnet")
  145. return execCompose(cli.testnet.Dir, "down")
  146. },
  147. })
  148. cli.root.AddCommand(&cobra.Command{
  149. Use: "pause",
  150. Short: "Pauses the Docker testnet",
  151. RunE: func(cmd *cobra.Command, args []string) error {
  152. logger.Info("Pausing testnet")
  153. return execCompose(cli.testnet.Dir, "pause")
  154. },
  155. })
  156. cli.root.AddCommand(&cobra.Command{
  157. Use: "resume",
  158. Short: "Resumes the Docker testnet",
  159. RunE: func(cmd *cobra.Command, args []string) error {
  160. logger.Info("Resuming testnet")
  161. return execCompose(cli.testnet.Dir, "unpause")
  162. },
  163. })
  164. cli.root.AddCommand(&cobra.Command{
  165. Use: "load [multiplier]",
  166. Args: cobra.MaximumNArgs(1),
  167. Short: "Generates transaction load until the command is canceled",
  168. RunE: func(cmd *cobra.Command, args []string) (err error) {
  169. m := 1
  170. if len(args) == 1 {
  171. m, err = strconv.Atoi(args[0])
  172. if err != nil {
  173. return err
  174. }
  175. }
  176. return Load(context.Background(), cli.testnet, m)
  177. },
  178. })
  179. cli.root.AddCommand(&cobra.Command{
  180. Use: "evidence [amount]",
  181. Args: cobra.MaximumNArgs(1),
  182. Short: "Generates and broadcasts evidence to a random node",
  183. RunE: func(cmd *cobra.Command, args []string) (err error) {
  184. amount := 1
  185. if len(args) == 1 {
  186. amount, err = strconv.Atoi(args[0])
  187. if err != nil {
  188. return err
  189. }
  190. }
  191. return InjectEvidence(cli.testnet, amount)
  192. },
  193. })
  194. cli.root.AddCommand(&cobra.Command{
  195. Use: "test",
  196. Short: "Runs test cases against a running testnet",
  197. RunE: func(cmd *cobra.Command, args []string) error {
  198. return Test(cli.testnet)
  199. },
  200. })
  201. cli.root.AddCommand(&cobra.Command{
  202. Use: "cleanup",
  203. Short: "Removes the testnet directory",
  204. RunE: func(cmd *cobra.Command, args []string) error {
  205. return Cleanup(cli.testnet)
  206. },
  207. })
  208. cli.root.AddCommand(&cobra.Command{
  209. Use: "logs [node]",
  210. Short: "Shows the testnet or a specefic node's logs",
  211. Example: "runner logs validator03",
  212. Args: cobra.MaximumNArgs(1),
  213. RunE: func(cmd *cobra.Command, args []string) error {
  214. return execComposeVerbose(cli.testnet.Dir, append([]string{"logs", "--no-color"}, args...)...)
  215. },
  216. })
  217. cli.root.AddCommand(&cobra.Command{
  218. Use: "tail [node]",
  219. Short: "Tails the testnet or a specific node's logs",
  220. Args: cobra.MaximumNArgs(1),
  221. RunE: func(cmd *cobra.Command, args []string) error {
  222. if len(args) == 1 {
  223. return execComposeVerbose(cli.testnet.Dir, "logs", "--follow", args[0])
  224. }
  225. return execComposeVerbose(cli.testnet.Dir, "logs", "--follow")
  226. },
  227. })
  228. cli.root.AddCommand(&cobra.Command{
  229. Use: "benchmark",
  230. Short: "Benchmarks testnet",
  231. Long: `Benchmarks the following metrics:
  232. Mean Block Interval
  233. Standard Deviation
  234. Min Block Interval
  235. Max Block Interval
  236. over a 100 block sampling period.
  237. Does not run any perbutations.
  238. `,
  239. RunE: func(cmd *cobra.Command, args []string) error {
  240. if err := Cleanup(cli.testnet); err != nil {
  241. return err
  242. }
  243. if err := Setup(cli.testnet); err != nil {
  244. return err
  245. }
  246. chLoadResult := make(chan error)
  247. ctx, loadCancel := context.WithCancel(context.Background())
  248. defer loadCancel()
  249. go func() {
  250. err := Load(ctx, cli.testnet, 1)
  251. chLoadResult <- err
  252. }()
  253. if err := Start(cli.testnet); err != nil {
  254. return err
  255. }
  256. if err := Wait(cli.testnet, 5); err != nil { // allow some txs to go through
  257. return err
  258. }
  259. // we benchmark performance over the next 100 blocks
  260. if err := Benchmark(cli.testnet, 100); err != nil {
  261. return err
  262. }
  263. loadCancel()
  264. if err := <-chLoadResult; err != nil {
  265. return err
  266. }
  267. if err := Cleanup(cli.testnet); err != nil {
  268. return err
  269. }
  270. return nil
  271. },
  272. })
  273. return cli
  274. }
  275. // Run runs the CLI.
  276. func (cli *CLI) Run() {
  277. if err := cli.root.Execute(); err != nil {
  278. logger.Error(err.Error())
  279. os.Exit(1)
  280. }
  281. }