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  }