go.etcd.io/etcd@v3.3.27+incompatible/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 "net/http" 23 "net/url" 24 "os" 25 "os/signal" 26 "syscall" 27 "time" 28 29 "github.com/coreos/etcd/pkg/proxy" 30 31 "go.uber.org/zap" 32 ) 33 34 var from string 35 var to string 36 var httpPort int 37 var verbose bool 38 39 func main() { 40 // TODO: support TLS 41 flag.StringVar(&from, "from", "localhost:23790", "Address URL to proxy from.") 42 flag.StringVar(&to, "to", "localhost:2379", "Address URL to forward.") 43 flag.IntVar(&httpPort, "http-port", 2378, "Port to serve etcd-proxy API.") 44 flag.BoolVar(&verbose, "verbose", false, "'true' to run proxy in verbose mode.") 45 46 flag.Usage = func() { 47 fmt.Fprintf(os.Stderr, "Usage of %q:\n", os.Args[0]) 48 fmt.Fprintln(os.Stderr, ` 49 etcd-proxy simulates various network conditions for etcd testing purposes. 50 See README.md for more examples. 51 52 Example: 53 54 # build etcd 55 $ ./build 56 $ ./bin/etcd 57 58 # build etcd-proxy 59 $ make build-etcd-proxy 60 61 # to test etcd with proxy layer 62 $ ./bin/etcd-proxy --help 63 $ ./bin/etcd-proxy --from localhost:23790 --to localhost:2379 --http-port 2378 --verbose 64 65 $ ETCDCTL_API=3 ./bin/etcdctl --endpoints localhost:2379 put foo bar 66 $ ETCDCTL_API=3 ./bin/etcdctl --endpoints localhost:23790 put foo bar`) 67 flag.PrintDefaults() 68 } 69 70 flag.Parse() 71 72 cfg := proxy.ServerConfig{ 73 From: url.URL{Scheme: "tcp", Host: from}, 74 To: url.URL{Scheme: "tcp", Host: to}, 75 } 76 if verbose { 77 cfg.Logger = zap.NewExample() 78 } 79 p := proxy.NewServer(cfg) 80 <-p.Ready() 81 defer p.Close() 82 83 mux := http.NewServeMux() 84 mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 85 w.Write([]byte(fmt.Sprintf("proxying [%s -> %s]\n", p.From(), p.To()))) 86 }) 87 mux.HandleFunc("/delay-tx", func(w http.ResponseWriter, req *http.Request) { 88 switch req.Method { 89 case http.MethodGet: 90 w.Write([]byte(fmt.Sprintf("current send latency %v\n", p.LatencyTx()))) 91 case http.MethodPut, http.MethodPost: 92 if err := req.ParseForm(); err != nil { 93 w.Write([]byte(fmt.Sprintf("wrong form %q\n", err.Error()))) 94 return 95 } 96 lat, err := time.ParseDuration(req.PostForm.Get("latency")) 97 if err != nil { 98 w.Write([]byte(fmt.Sprintf("wrong latency form %q\n", err.Error()))) 99 return 100 } 101 rv, err := time.ParseDuration(req.PostForm.Get("random-variable")) 102 if err != nil { 103 w.Write([]byte(fmt.Sprintf("wrong random-variable form %q\n", err.Error()))) 104 return 105 } 106 p.DelayTx(lat, rv) 107 w.Write([]byte(fmt.Sprintf("added send latency %v±%v (current latency %v)\n", lat, rv, p.LatencyTx()))) 108 case http.MethodDelete: 109 lat := p.LatencyTx() 110 p.UndelayTx() 111 w.Write([]byte(fmt.Sprintf("removed latency %v\n", lat))) 112 default: 113 w.Write([]byte(fmt.Sprintf("unsupported method %q\n", req.Method))) 114 } 115 }) 116 mux.HandleFunc("/delay-rx", func(w http.ResponseWriter, req *http.Request) { 117 switch req.Method { 118 case http.MethodGet: 119 w.Write([]byte(fmt.Sprintf("current receive latency %v\n", p.LatencyRx()))) 120 case http.MethodPut, http.MethodPost: 121 if err := req.ParseForm(); err != nil { 122 w.Write([]byte(fmt.Sprintf("wrong form %q\n", err.Error()))) 123 return 124 } 125 lat, err := time.ParseDuration(req.PostForm.Get("latency")) 126 if err != nil { 127 w.Write([]byte(fmt.Sprintf("wrong latency form %q\n", err.Error()))) 128 return 129 } 130 rv, err := time.ParseDuration(req.PostForm.Get("random-variable")) 131 if err != nil { 132 w.Write([]byte(fmt.Sprintf("wrong random-variable form %q\n", err.Error()))) 133 return 134 } 135 p.DelayRx(lat, rv) 136 w.Write([]byte(fmt.Sprintf("added receive latency %v±%v (current latency %v)\n", lat, rv, p.LatencyRx()))) 137 case http.MethodDelete: 138 lat := p.LatencyRx() 139 p.UndelayRx() 140 w.Write([]byte(fmt.Sprintf("removed latency %v\n", lat))) 141 default: 142 w.Write([]byte(fmt.Sprintf("unsupported method %q\n", req.Method))) 143 } 144 }) 145 mux.HandleFunc("/pause-tx", func(w http.ResponseWriter, req *http.Request) { 146 switch req.Method { 147 case http.MethodPut, http.MethodPost: 148 p.PauseTx() 149 w.Write([]byte(fmt.Sprintf("paused forwarding [%s -> %s]\n", p.From(), p.To()))) 150 case http.MethodDelete: 151 p.UnpauseTx() 152 w.Write([]byte(fmt.Sprintf("unpaused forwarding [%s -> %s]\n", p.From(), p.To()))) 153 default: 154 w.Write([]byte(fmt.Sprintf("unsupported method %q\n", req.Method))) 155 } 156 }) 157 mux.HandleFunc("/pause-rx", func(w http.ResponseWriter, req *http.Request) { 158 switch req.Method { 159 case http.MethodPut, http.MethodPost: 160 p.PauseRx() 161 w.Write([]byte(fmt.Sprintf("paused forwarding [%s <- %s]\n", p.From(), p.To()))) 162 case http.MethodDelete: 163 p.UnpauseRx() 164 w.Write([]byte(fmt.Sprintf("unpaused forwarding [%s <- %s]\n", p.From(), p.To()))) 165 default: 166 w.Write([]byte(fmt.Sprintf("unsupported method %q\n", req.Method))) 167 } 168 }) 169 mux.HandleFunc("/blackhole-tx", func(w http.ResponseWriter, req *http.Request) { 170 switch req.Method { 171 case http.MethodPut, http.MethodPost: 172 p.BlackholeTx() 173 w.Write([]byte(fmt.Sprintf("blackholed; dropping packets [%s -> %s]\n", p.From(), p.To()))) 174 case http.MethodDelete: 175 p.UnblackholeTx() 176 w.Write([]byte(fmt.Sprintf("unblackholed; restart forwarding [%s -> %s]\n", p.From(), p.To()))) 177 default: 178 w.Write([]byte(fmt.Sprintf("unsupported method %q\n", req.Method))) 179 } 180 }) 181 mux.HandleFunc("/blackhole-rx", func(w http.ResponseWriter, req *http.Request) { 182 switch req.Method { 183 case http.MethodPut, http.MethodPost: 184 p.BlackholeRx() 185 w.Write([]byte(fmt.Sprintf("blackholed; dropping packets [%s <- %s]\n", p.From(), p.To()))) 186 case http.MethodDelete: 187 p.UnblackholeRx() 188 w.Write([]byte(fmt.Sprintf("unblackholed; restart forwarding [%s <- %s]\n", p.From(), p.To()))) 189 default: 190 w.Write([]byte(fmt.Sprintf("unsupported method %q\n", req.Method))) 191 } 192 }) 193 srv := &http.Server{ 194 Addr: fmt.Sprintf(":%d", httpPort), 195 Handler: mux, 196 } 197 defer srv.Close() 198 199 sig := make(chan os.Signal, 1) 200 signal.Notify(sig, os.Interrupt, syscall.SIGTERM) 201 defer signal.Stop(sig) 202 203 go func() { 204 s := <-sig 205 fmt.Printf("\n\nreceived signal %q, shutting down HTTP server\n\n", s) 206 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) 207 err := srv.Shutdown(ctx) 208 cancel() 209 fmt.Printf("gracefully stopped HTTP server with %v\n\n", err) 210 os.Exit(0) 211 }() 212 213 fmt.Printf("\nserving HTTP server http://localhost:%d\n\n", httpPort) 214 err := srv.ListenAndServe() 215 fmt.Printf("HTTP server exit with error %v\n", err) 216 }