1 package main
2
3 import (
4 "context"
5 "encoding/json"
6 "flag"
7 "fmt"
8 "os"
9 "strconv"
10 "strings"
11 "time"
12
13 "github.com/letsencrypt/boulder/cmd"
14 )
15
16 type Config struct {
17
18 Plan struct {
19 Actions []string
20 Rate int64
21 RateDelta string
22 Runtime string
23 }
24 ExternalState string
25 DontSaveState bool
26 DirectoryURL string
27 DomainBase string
28 HTTPOneAddrs []string
29 TLSALPNOneAddrs []string
30 DNSAddrs []string
31 FakeDNS string
32 RealIP string
33 RegEmail string
34 Results string
35 MaxRegs int
36 MaxNamesPerCert int
37 ChallengeStrategy string
38 RevokeChance float32
39 }
40
41 func main() {
42 configPath := flag.String("config", "", "Path to configuration file for load-generator")
43 resultsPath := flag.String("results", "", "Path to latency results file")
44 rateArg := flag.Int("rate", 0, "")
45 runtimeArg := flag.String("runtime", "", "")
46 deltaArg := flag.String("delta", "", "")
47 flag.Parse()
48
49 if *configPath == "" {
50 fmt.Fprintf(os.Stderr, "-config argument must not be empty\n")
51 os.Exit(1)
52 }
53
54 configBytes, err := os.ReadFile(*configPath)
55 if err != nil {
56 fmt.Fprintf(os.Stderr, "Failed to read load-generator config file %q: %s\n", *configPath, err)
57 os.Exit(1)
58 }
59 var config Config
60 err = json.Unmarshal(configBytes, &config)
61 if err != nil {
62 fmt.Fprintf(os.Stderr, "Failed to parse load-generator config file: %s\n", err)
63 os.Exit(1)
64 }
65
66 if *resultsPath != "" {
67 config.Results = *resultsPath
68 }
69 if *rateArg != 0 {
70 config.Plan.Rate = int64(*rateArg)
71 }
72 if *runtimeArg != "" {
73 config.Plan.Runtime = *runtimeArg
74 }
75 if *deltaArg != "" {
76 config.Plan.RateDelta = *deltaArg
77 }
78
79 s, err := New(
80 config.DirectoryURL,
81 config.DomainBase,
82 config.RealIP,
83 config.MaxRegs,
84 config.MaxNamesPerCert,
85 config.Results,
86 config.RegEmail,
87 config.Plan.Actions,
88 config.ChallengeStrategy,
89 config.RevokeChance,
90 )
91 cmd.FailOnError(err, "Failed to create load generator")
92
93 if config.ExternalState != "" {
94 err = s.Restore(config.ExternalState)
95 cmd.FailOnError(err, "Failed to load registration snapshot")
96 }
97
98 runtime, err := time.ParseDuration(config.Plan.Runtime)
99 cmd.FailOnError(err, "Failed to parse plan runtime")
100
101 var delta *RateDelta
102 if config.Plan.RateDelta != "" {
103 parts := strings.Split(config.Plan.RateDelta, "/")
104 if len(parts) != 2 {
105 fmt.Fprintf(os.Stderr, "RateDelta is malformed")
106 os.Exit(1)
107 }
108 rate, err := strconv.Atoi(parts[0])
109 cmd.FailOnError(err, "Failed to parse increase portion of RateDelta")
110 period, err := time.ParseDuration(parts[1])
111 cmd.FailOnError(err, "Failed to parse period portion of RateDelta")
112 delta = &RateDelta{Inc: int64(rate), Period: period}
113 }
114
115 if len(config.HTTPOneAddrs) == 0 &&
116 len(config.TLSALPNOneAddrs) == 0 &&
117 len(config.DNSAddrs) == 0 {
118 cmd.Fail("There must be at least one bind address in " +
119 "HTTPOneAddrs, TLSALPNOneAddrs or DNSAddrs\n")
120 }
121
122 ctx, cancel := context.WithCancel(context.Background())
123 go cmd.CatchSignals(cancel)
124
125 err = s.Run(
126 ctx,
127 config.HTTPOneAddrs,
128 config.TLSALPNOneAddrs,
129 config.DNSAddrs,
130 config.FakeDNS,
131 Plan{
132 Runtime: runtime,
133 Rate: config.Plan.Rate,
134 Delta: delta,
135 })
136 cmd.FailOnError(err, "Failed to run load generator")
137
138 if config.ExternalState != "" && !config.DontSaveState {
139 err = s.Snapshot(config.ExternalState)
140 cmd.FailOnError(err, "Failed to save registration snapshot")
141 }
142
143 fmt.Println("[+] All done, bye bye ^_^")
144 }
145
View as plain text