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.

357 lines
8.7 KiB

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