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.

365 lines
9.1 KiB

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