github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/grid/debug.go (about)

     1  // Copyright (c) 2015-2023 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package grid
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"net"
    24  	"net/http"
    25  	"net/http/httptest"
    26  	"sync"
    27  	"time"
    28  
    29  	xioutil "github.com/minio/minio/internal/ioutil"
    30  	"github.com/minio/mux"
    31  )
    32  
    33  //go:generate stringer -type=debugMsg $GOFILE
    34  
    35  // debugMsg is a debug message for testing purposes.
    36  // may only be used for tests.
    37  type debugMsg int
    38  
    39  const (
    40  	debugPrint = false
    41  	debugReqs  = false
    42  )
    43  
    44  const (
    45  	debugShutdown debugMsg = iota
    46  	debugKillInbound
    47  	debugKillOutbound
    48  	debugWaitForExit
    49  	debugSetConnPingDuration
    50  	debugSetClientPingDuration
    51  	debugAddToDeadline
    52  	debugIsOutgoingClosed
    53  )
    54  
    55  // TestGrid contains a grid of servers for testing purposes.
    56  type TestGrid struct {
    57  	Servers     []*httptest.Server
    58  	Listeners   []net.Listener
    59  	Managers    []*Manager
    60  	Mux         []*mux.Router
    61  	Hosts       []string
    62  	cleanupOnce sync.Once
    63  	cancel      context.CancelFunc
    64  }
    65  
    66  // SetupTestGrid creates a new grid for testing purposes.
    67  // Select the number of hosts to create.
    68  // Call (TestGrid).Cleanup() when done.
    69  func SetupTestGrid(n int) (*TestGrid, error) {
    70  	hosts, listeners, err := getHosts(n)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	dialer := &net.Dialer{
    75  		Timeout: 5 * time.Second,
    76  	}
    77  	var res TestGrid
    78  	res.Hosts = hosts
    79  	ready := make(chan struct{})
    80  	ctx, cancel := context.WithCancel(context.Background())
    81  	res.cancel = cancel
    82  	for i, host := range hosts {
    83  		manager, err := NewManager(ctx, ManagerOptions{
    84  			Dialer: dialer.DialContext,
    85  			Local:  host,
    86  			Hosts:  hosts,
    87  			AuthRequest: func(r *http.Request) error {
    88  				return nil
    89  			},
    90  			AddAuth:      func(aud string) string { return aud },
    91  			BlockConnect: ready,
    92  		})
    93  		if err != nil {
    94  			return nil, err
    95  		}
    96  		m := mux.NewRouter()
    97  		m.Handle(RoutePath, manager.Handler())
    98  		res.Managers = append(res.Managers, manager)
    99  		res.Servers = append(res.Servers, startHTTPServer(listeners[i], m))
   100  		res.Listeners = append(res.Listeners, listeners[i])
   101  		res.Mux = append(res.Mux, m)
   102  	}
   103  	xioutil.SafeClose(ready)
   104  	for _, m := range res.Managers {
   105  		for _, remote := range m.Targets() {
   106  			if err := m.Connection(remote).WaitForConnect(ctx); err != nil {
   107  				return nil, err
   108  			}
   109  		}
   110  	}
   111  	return &res, nil
   112  }
   113  
   114  // Cleanup will clean up the test grid.
   115  func (t *TestGrid) Cleanup() {
   116  	t.cancel()
   117  	t.cleanupOnce.Do(func() {
   118  		for _, manager := range t.Managers {
   119  			manager.debugMsg(debugShutdown)
   120  		}
   121  		for _, server := range t.Servers {
   122  			server.Close()
   123  		}
   124  		for _, listener := range t.Listeners {
   125  			listener.Close()
   126  		}
   127  	})
   128  }
   129  
   130  // WaitAllConnect will wait for all connections to be established.
   131  func (t *TestGrid) WaitAllConnect(ctx context.Context) {
   132  	for _, manager := range t.Managers {
   133  		for _, remote := range manager.Targets() {
   134  			if manager.HostName() == remote {
   135  				continue
   136  			}
   137  			if err := manager.Connection(remote).WaitForConnect(ctx); err != nil {
   138  				panic(err)
   139  			}
   140  		}
   141  	}
   142  }
   143  
   144  func getHosts(n int) (hosts []string, listeners []net.Listener, err error) {
   145  	for i := 0; i < n; i++ {
   146  		l, err := net.Listen("tcp", "127.0.0.1:0")
   147  		if err != nil {
   148  			if l, err = net.Listen("tcp6", "[::1]:0"); err != nil {
   149  				return nil, nil, fmt.Errorf("httptest: failed to listen on a port: %v", err)
   150  			}
   151  		}
   152  		addr := l.Addr()
   153  		hosts = append(hosts, "http://"+addr.String())
   154  		listeners = append(listeners, l)
   155  	}
   156  	return
   157  }
   158  
   159  func startHTTPServer(listener net.Listener, handler http.Handler) (server *httptest.Server) {
   160  	server = httptest.NewUnstartedServer(handler)
   161  	server.Config.Addr = listener.Addr().String()
   162  	server.Listener = listener
   163  	server.Start()
   164  	return server
   165  }