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  }