github.com/lfch/etcd-io/tests/v3@v3.0.0-20221004140520-eac99acd3e9d/functional/cmd/etcd-proxy/main.go (about) 1 // Copyright 2018 The etcd Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // etcd-proxy is a proxy layer that simulates various network conditions. 16 package main 17 18 import ( 19 "context" 20 "flag" 21 "fmt" 22 "io" 23 "log" 24 "net/http" 25 "net/url" 26 "os" 27 "os/signal" 28 "syscall" 29 "time" 30 31 "github.com/lfch/etcd-io/pkg/v3/proxy" 32 33 "go.uber.org/zap" 34 ) 35 36 var from string 37 var to string 38 var httpPort int 39 var verbose bool 40 41 func main() { 42 // TODO: support TLS 43 flag.StringVar(&from, "from", "localhost:23790", "Address URL to proxy from.") 44 flag.StringVar(&to, "to", "localhost:2379", "Address URL to forward.") 45 flag.IntVar(&httpPort, "http-port", 2378, "Port to serve etcd-proxy API.") 46 flag.BoolVar(&verbose, "verbose", false, "'true' to run proxy in verbose mode.") 47 48 flag.Usage = func() { 49 fmt.Fprintf(os.Stderr, "Usage of %q:\n", os.Args[0]) 50 fmt.Fprintln(os.Stderr, ` 51 etcd-proxy simulates various network conditions for etcd testing purposes. 52 See README.md for more examples. 53 54 Example: 55 56 # build etcd 57 $ ./scripts/build.sh 58 $ ./bin/etcd 59 60 # build etcd-proxy 61 $ make build-etcd-proxy 62 63 # to test etcd with proxy layer 64 $ ./bin/etcd-proxy --help 65 $ ./bin/etcd-proxy --from localhost:23790 --to localhost:2379 --http-port 2378 --verbose 66 67 $ ./bin/etcdctl --endpoints localhost:2379 put foo bar 68 $ ./bin/etcdctl --endpoints localhost:23790 put foo bar`) 69 flag.PrintDefaults() 70 } 71 72 flag.Parse() 73 74 cfg := proxy.ServerConfig{ 75 From: url.URL{Scheme: "tcp", Host: from}, 76 To: url.URL{Scheme: "tcp", Host: to}, 77 } 78 if verbose { 79 var err error 80 cfg.Logger, err = zap.NewDevelopment() 81 if err != nil { 82 panic(err) 83 } 84 cfg.Logger = cfg.Logger.Named("proxy").With( 85 zap.String("from", from), 86 zap.String("to", to), 87 zap.Int("port", httpPort)) 88 } 89 p := proxy.NewServer(cfg) 90 91 select { 92 case <-p.Ready(): 93 case err := <-p.Error(): 94 panic(err) 95 } 96 97 defer p.Close() 98 99 mux := http.NewServeMux() 100 mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 101 w.Write([]byte(fmt.Sprintf("proxying [%s -> %s]\n", p.From(), p.To()))) 102 }) 103 mux.HandleFunc("/delay-tx", func(w http.ResponseWriter, req *http.Request) { 104 switch req.Method { 105 case http.MethodGet: 106 w.Write([]byte(fmt.Sprintf("current send latency %v\n", p.LatencyTx()))) 107 case http.MethodPut, http.MethodPost: 108 if err := req.ParseForm(); err != nil { 109 w.Write([]byte(fmt.Sprintf("wrong form %q\n", err.Error()))) 110 return 111 } 112 lat, err := time.ParseDuration(req.PostForm.Get("latency")) 113 if err != nil { 114 w.Write([]byte(fmt.Sprintf("wrong latency form %q\n", err.Error()))) 115 return 116 } 117 rv, err := time.ParseDuration(req.PostForm.Get("random-variable")) 118 if err != nil { 119 w.Write([]byte(fmt.Sprintf("wrong random-variable form %q\n", err.Error()))) 120 return 121 } 122 p.DelayTx(lat, rv) 123 w.Write([]byte(fmt.Sprintf("added send latency %v±%v (current latency %v)\n", lat, rv, p.LatencyTx()))) 124 case http.MethodDelete: 125 lat := p.LatencyTx() 126 p.UndelayTx() 127 w.Write([]byte(fmt.Sprintf("removed latency %v\n", lat))) 128 default: 129 w.Write([]byte(fmt.Sprintf("unsupported method %q\n", req.Method))) 130 } 131 }) 132 mux.HandleFunc("/delay-rx", func(w http.ResponseWriter, req *http.Request) { 133 switch req.Method { 134 case http.MethodGet: 135 w.Write([]byte(fmt.Sprintf("current receive latency %v\n", p.LatencyRx()))) 136 case http.MethodPut, http.MethodPost: 137 if err := req.ParseForm(); err != nil { 138 w.Write([]byte(fmt.Sprintf("wrong form %q\n", err.Error()))) 139 return 140 } 141 lat, err := time.ParseDuration(req.PostForm.Get("latency")) 142 if err != nil { 143 w.Write([]byte(fmt.Sprintf("wrong latency form %q\n", err.Error()))) 144 return 145 } 146 rv, err := time.ParseDuration(req.PostForm.Get("random-variable")) 147 if err != nil { 148 w.Write([]byte(fmt.Sprintf("wrong random-variable form %q\n", err.Error()))) 149 return 150 } 151 p.DelayRx(lat, rv) 152 w.Write([]byte(fmt.Sprintf("added receive latency %v±%v (current latency %v)\n", lat, rv, p.LatencyRx()))) 153 case http.MethodDelete: 154 lat := p.LatencyRx() 155 p.UndelayRx() 156 w.Write([]byte(fmt.Sprintf("removed latency %v\n", lat))) 157 default: 158 w.Write([]byte(fmt.Sprintf("unsupported method %q\n", req.Method))) 159 } 160 }) 161 mux.HandleFunc("/pause-tx", func(w http.ResponseWriter, req *http.Request) { 162 switch req.Method { 163 case http.MethodPut, http.MethodPost: 164 p.PauseTx() 165 w.Write([]byte(fmt.Sprintf("paused forwarding [%s -> %s]\n", p.From(), p.To()))) 166 case http.MethodDelete: 167 p.UnpauseTx() 168 w.Write([]byte(fmt.Sprintf("unpaused forwarding [%s -> %s]\n", p.From(), p.To()))) 169 default: 170 w.Write([]byte(fmt.Sprintf("unsupported method %q\n", req.Method))) 171 } 172 }) 173 mux.HandleFunc("/pause-rx", func(w http.ResponseWriter, req *http.Request) { 174 switch req.Method { 175 case http.MethodPut, http.MethodPost: 176 p.PauseRx() 177 w.Write([]byte(fmt.Sprintf("paused forwarding [%s <- %s]\n", p.From(), p.To()))) 178 case http.MethodDelete: 179 p.UnpauseRx() 180 w.Write([]byte(fmt.Sprintf("unpaused forwarding [%s <- %s]\n", p.From(), p.To()))) 181 default: 182 w.Write([]byte(fmt.Sprintf("unsupported method %q\n", req.Method))) 183 } 184 }) 185 mux.HandleFunc("/blackhole-tx", func(w http.ResponseWriter, req *http.Request) { 186 switch req.Method { 187 case http.MethodPut, http.MethodPost: 188 p.BlackholeTx() 189 w.Write([]byte(fmt.Sprintf("blackholed; dropping packets [%s -> %s]\n", p.From(), p.To()))) 190 case http.MethodDelete: 191 p.UnblackholeTx() 192 w.Write([]byte(fmt.Sprintf("unblackholed; restart forwarding [%s -> %s]\n", p.From(), p.To()))) 193 default: 194 w.Write([]byte(fmt.Sprintf("unsupported method %q\n", req.Method))) 195 } 196 }) 197 mux.HandleFunc("/blackhole-rx", func(w http.ResponseWriter, req *http.Request) { 198 switch req.Method { 199 case http.MethodPut, http.MethodPost: 200 p.BlackholeRx() 201 w.Write([]byte(fmt.Sprintf("blackholed; dropping packets [%s <- %s]\n", p.From(), p.To()))) 202 case http.MethodDelete: 203 p.UnblackholeRx() 204 w.Write([]byte(fmt.Sprintf("unblackholed; restart forwarding [%s <- %s]\n", p.From(), p.To()))) 205 default: 206 w.Write([]byte(fmt.Sprintf("unsupported method %q\n", req.Method))) 207 } 208 }) 209 srv := &http.Server{ 210 Addr: fmt.Sprintf(":%d", httpPort), 211 Handler: mux, 212 ErrorLog: log.New(io.Discard, "net/http", 0), 213 } 214 defer srv.Close() 215 216 sig := make(chan os.Signal, 1) 217 signal.Notify(sig, os.Interrupt, syscall.SIGTERM) 218 defer signal.Stop(sig) 219 220 go func() { 221 s := <-sig 222 fmt.Printf("\n\nreceived signal %q, shutting down HTTP server\n\n", s) 223 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 224 err := srv.Shutdown(ctx) 225 cancel() 226 fmt.Printf("gracefully stopped HTTP server with %v\n\n", err) 227 os.Exit(0) 228 }() 229 230 fmt.Printf("\nserving HTTP server http://localhost:%d\n\n", httpPort) 231 err := srv.ListenAndServe() 232 fmt.Printf("HTTP server exit with error %v\n", err) 233 }