github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ais/multiproxy_internal_test.go (about)

     1  // Package ais provides core functionality for the AIStore object storage.
     2  /*
     3   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package ais
     6  
     7  import (
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/NVIDIA/aistore/api/apc"
    14  	"github.com/NVIDIA/aistore/cmn"
    15  	"github.com/NVIDIA/aistore/cmn/atomic"
    16  	"github.com/NVIDIA/aistore/cmn/cifl"
    17  	"github.com/NVIDIA/aistore/cmn/cos"
    18  	"github.com/NVIDIA/aistore/core/meta"
    19  	"github.com/NVIDIA/aistore/core/mock"
    20  	"github.com/NVIDIA/aistore/tools"
    21  	jsoniter "github.com/json-iterator/go"
    22  )
    23  
    24  type (
    25  	discoverServerHandler func(sv int64, lv int64) *httptest.Server
    26  
    27  	discoverServer struct {
    28  		id          string
    29  		isProxy     bool
    30  		smapVersion int64
    31  		bmdVersion  int64
    32  		httpHandler discoverServerHandler
    33  	}
    34  )
    35  
    36  func newSnode(id, daeType string, publicNet, intraControlNet, intraDataNet meta.NetInfo) (snode *meta.Snode) {
    37  	snode = &meta.Snode{PubNet: publicNet, ControlNet: intraControlNet, DataNet: intraDataNet}
    38  	snode.Init(id, daeType)
    39  	return
    40  }
    41  
    42  // newDiscoverServerPrimary returns a proxy runner after initializing the fields that are needed by this test
    43  func newDiscoverServerPrimary() *proxy {
    44  	var (
    45  		p       = &proxy{}
    46  		tracker = mock.NewStatsTracker()
    47  	)
    48  	p.si = newSnode("primary", apc.Proxy, meta.NetInfo{}, meta.NetInfo{}, meta.NetInfo{})
    49  
    50  	g.client.data = &http.Client{}
    51  	g.client.control = &http.Client{}
    52  
    53  	config := cmn.GCO.BeginUpdate()
    54  	config.Keepalive.Proxy.Name = "heartbeat"
    55  	config.Timeout.Startup = cos.Duration(4 * time.Second)
    56  	config.Timeout.CplaneOperation = cos.Duration(2 * time.Second)
    57  	config.Timeout.MaxKeepalive = cos.Duration(4 * time.Second)
    58  	config.Client.Timeout = cos.Duration(10 * time.Second)
    59  	config.Client.TimeoutLong = cos.Duration(10 * time.Second)
    60  	config.Cksum.Type = cos.ChecksumXXHash
    61  	cmn.GCO.CommitUpdate(config)
    62  
    63  	p.owner.smap = newSmapOwner(config)
    64  	p.owner.smap.put(newSmap())
    65  	owner := newBMDOwnerPrx(config)
    66  	owner.put(newBucketMD())
    67  	p.owner.bmd = owner
    68  	p.keepalive = newPalive(p, tracker, atomic.NewBool(true))
    69  	return p
    70  }
    71  
    72  // discoverServerDefaultHandler returns the Smap and BMD with the given version
    73  func discoverServerDefaultHandler(sv, lv int64) *httptest.Server {
    74  	smapVersion := sv
    75  	bmdVersion := lv
    76  	return httptest.NewServer(http.HandlerFunc(
    77  		func(w http.ResponseWriter, _ *http.Request) {
    78  			cm := cluMeta{
    79  				Smap: &smapX{Smap: meta.Smap{Version: smapVersion}},
    80  				BMD:  &bucketMD{BMD: meta.BMD{Version: bmdVersion}},
    81  			}
    82  			cm.Flags = cm.Flags.Clear(cifl.VoteInProgress)
    83  			b, _ := jsoniter.Marshal(cm)
    84  			w.Write(b)
    85  		},
    86  	))
    87  }
    88  
    89  // discoverServerVoteOnceHandler returns vote in progress on the first time it is call, returns
    90  // Smap and BMD on subsequent calls
    91  func discoverServerVoteOnceHandler(sv, lv int64) *httptest.Server {
    92  	cnt := 0
    93  	smapVersion := sv
    94  	bmdVersion := lv
    95  	f := func(w http.ResponseWriter, _ *http.Request) {
    96  		cnt++
    97  		cm := cluMeta{
    98  			Smap: &smapX{Smap: meta.Smap{Version: smapVersion}},
    99  			BMD:  &bucketMD{BMD: meta.BMD{Version: bmdVersion}},
   100  		}
   101  		if cnt == 1 {
   102  			cm.Flags = cm.Flags.Set(cifl.VoteInProgress)
   103  		}
   104  		b, _ := jsoniter.Marshal(cm)
   105  		w.Write(b)
   106  	}
   107  
   108  	return httptest.NewServer(http.HandlerFunc(f))
   109  }
   110  
   111  // discoverServerFailTwiceHandler fails the first two calls and returns
   112  // Smap abd BMD on subsequent calls
   113  func discoverServerFailTwiceHandler(sv, lv int64) *httptest.Server {
   114  	cnt := 0
   115  	smapVersion := sv
   116  	bmdVersion := lv
   117  	f := func(w http.ResponseWriter, _ *http.Request) {
   118  		cnt++
   119  		if cnt > 2 {
   120  			cm := cluMeta{
   121  				Smap: &smapX{Smap: meta.Smap{Version: smapVersion}},
   122  				BMD:  &bucketMD{BMD: meta.BMD{Version: bmdVersion}},
   123  			}
   124  			cm.Flags = cm.Flags.Clear(cifl.VoteInProgress)
   125  			b, _ := jsoniter.Marshal(cm)
   126  			w.Write(b)
   127  		} else {
   128  			http.Error(w, "retry", http.StatusUnavailableForLegalReasons)
   129  		}
   130  	}
   131  
   132  	return httptest.NewServer(http.HandlerFunc(f))
   133  }
   134  
   135  // discoverServerAlwaysFailHandler always responds with error
   136  func discoverServerAlwaysFailHandler(_, _ int64) *httptest.Server {
   137  	f := func(w http.ResponseWriter, _ *http.Request) {
   138  		http.Error(w, "retry", http.StatusUnavailableForLegalReasons)
   139  	}
   140  
   141  	return httptest.NewServer(http.HandlerFunc(f))
   142  }
   143  
   144  // discoverServerVoteInProgressHandler always responds with vote in progress
   145  func discoverServerVoteInProgressHandler(_, _ int64) *httptest.Server {
   146  	return httptest.NewServer(http.HandlerFunc(
   147  		func(w http.ResponseWriter, _ *http.Request) {
   148  			cm := cluMeta{
   149  				Smap: &smapX{Smap: meta.Smap{Version: 12345}},
   150  				BMD:  &bucketMD{BMD: meta.BMD{Version: 67890}},
   151  			}
   152  			cm.Flags = cm.Flags.Set(cifl.VoteInProgress)
   153  			b, _ := jsoniter.Marshal(cm)
   154  			w.Write(b)
   155  		},
   156  	))
   157  }
   158  
   159  func TestDiscoverServers(t *testing.T) {
   160  	tcs := []struct {
   161  		name        string
   162  		servers     []discoverServer
   163  		duration    time.Duration // how long to wait for discover servers call
   164  		smapVersion int64         // expected return from discover servers
   165  		bmdVersion  int64         // use '0' if expecting nil Smap or BMD
   166  		onlyLong    bool          // run only in long mode
   167  	}{
   168  		{
   169  			"empty discovery Smap",
   170  			[]discoverServer{},
   171  			time.Millisecond,
   172  			0,
   173  			0,
   174  			false,
   175  		},
   176  		{
   177  			"all agreed",
   178  			[]discoverServer{
   179  				{"p1", true, 1, 2, discoverServerDefaultHandler},
   180  				{"t1", false, 1, 2, discoverServerDefaultHandler},
   181  			},
   182  			time.Millisecond,
   183  			1,
   184  			2,
   185  			false,
   186  		},
   187  		{
   188  			"mixed",
   189  			[]discoverServer{
   190  				{"p1", true, 1, 2, discoverServerDefaultHandler},
   191  				{"t1", false, 4, 5, discoverServerDefaultHandler},
   192  				{"t2", false, 1, 2, discoverServerDefaultHandler},
   193  			},
   194  			time.Millisecond,
   195  			4,
   196  			5,
   197  			false,
   198  		},
   199  		{
   200  			"voting",
   201  			[]discoverServer{
   202  				{"t1", false, 4, 5, discoverServerVoteInProgressHandler},
   203  				{"t2", false, 1, 2, discoverServerVoteInProgressHandler},
   204  			},
   205  			time.Millisecond * 300,
   206  			0,
   207  			0,
   208  			true,
   209  		},
   210  		{
   211  			"voting and map mixed",
   212  			[]discoverServer{
   213  				{"t1", false, 4, 5, discoverServerVoteInProgressHandler},
   214  				{"t2", false, 1, 2, discoverServerDefaultHandler},
   215  			},
   216  			time.Millisecond * 300,
   217  			0,
   218  			0,
   219  			true,
   220  		},
   221  		{
   222  			"vote once",
   223  			[]discoverServer{
   224  				{"t1", false, 4, 5, discoverServerVoteOnceHandler},
   225  				{"t2", false, 1, 2, discoverServerDefaultHandler},
   226  			},
   227  			time.Millisecond * 3000,
   228  			4,
   229  			5,
   230  			false,
   231  		},
   232  		{
   233  			"fail twice",
   234  			[]discoverServer{
   235  				{"t1", false, 4, 5, discoverServerFailTwiceHandler},
   236  				{"t2", false, 1, 2, discoverServerDefaultHandler},
   237  			},
   238  			time.Millisecond * 3000,
   239  			4,
   240  			5,
   241  			true,
   242  		},
   243  		{
   244  			"all failed",
   245  			[]discoverServer{
   246  				{"t1", false, 4, 5, discoverServerAlwaysFailHandler},
   247  				{"t2", false, 1, 2, discoverServerAlwaysFailHandler},
   248  			},
   249  			time.Millisecond * 400,
   250  			0,
   251  			0,
   252  			true,
   253  		},
   254  		{
   255  			"fail and good mixed",
   256  			[]discoverServer{
   257  				{"t1", false, 4, 5, discoverServerDefaultHandler},
   258  				{"t2", false, 1, 2, discoverServerAlwaysFailHandler},
   259  			},
   260  			time.Millisecond * 400,
   261  			4,
   262  			5,
   263  			true,
   264  		},
   265  		{
   266  			"zero Smap version",
   267  			[]discoverServer{
   268  				{"p1", true, 0, 3, discoverServerDefaultHandler},
   269  				{"t1", false, 0, 4, discoverServerDefaultHandler},
   270  			},
   271  			time.Millisecond * 400,
   272  			0,
   273  			4,
   274  			false,
   275  		},
   276  		{
   277  			"zero BMD version",
   278  			[]discoverServer{
   279  				{"p1", true, 1, 0, discoverServerDefaultHandler},
   280  				{"t1", false, 1, 0, discoverServerDefaultHandler},
   281  			},
   282  			time.Millisecond * 400,
   283  			1,
   284  			0,
   285  			false,
   286  		},
   287  	}
   288  
   289  	for _, tc := range tcs {
   290  		t.Run(tc.name, func(t *testing.T) {
   291  			tools.CheckSkip(t, &tools.SkipTestArgs{Long: tc.onlyLong})
   292  			var (
   293  				primary      = newDiscoverServerPrimary()
   294  				discoverSmap = newSmap()
   295  			)
   296  
   297  			for _, s := range tc.servers {
   298  				ts := s.httpHandler(s.smapVersion, s.bmdVersion)
   299  				addrInfo := serverTCPAddr(ts.URL)
   300  				if s.isProxy {
   301  					discoverSmap.addProxy(newSnode(s.id, apc.Proxy, addrInfo, addrInfo, addrInfo))
   302  				} else {
   303  					discoverSmap.addTarget(newSnode(s.id, apc.Target, addrInfo, addrInfo, addrInfo))
   304  				}
   305  			}
   306  			svm := primary.uncoverMeta(discoverSmap)
   307  			if tc.smapVersion == 0 {
   308  				if svm.Smap != nil && svm.Smap.version() > 0 {
   309  					t.Errorf("test case %q: expecting nil Smap", tc.name)
   310  				}
   311  			} else {
   312  				if svm.Smap == nil || svm.Smap.version() == 0 {
   313  					t.Errorf("test case %q: expecting non-empty Smap", tc.name)
   314  				} else if tc.smapVersion != svm.Smap.Version {
   315  					t.Errorf("test case %q: expecting %d, got %d", tc.name, tc.smapVersion, svm.Smap.Version)
   316  				}
   317  			}
   318  
   319  			if tc.bmdVersion == 0 {
   320  				if svm.BMD != nil && svm.BMD.version() > 0 {
   321  					t.Errorf("test case %q: expecting nil BMD", tc.name)
   322  				}
   323  			} else {
   324  				if svm.BMD == nil || svm.BMD.version() == 0 {
   325  					t.Errorf("test case %q: expecting non-empty BMD", tc.name)
   326  				} else if tc.bmdVersion != svm.BMD.Version {
   327  					t.Errorf("test case %q: expecting %d, got %d", tc.name, tc.bmdVersion, svm.BMD.Version)
   328  				}
   329  			}
   330  		})
   331  	}
   332  }