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

     1  // Package transport provides long-lived http/tcp connections for
     2  // intra-cluster communications (see README for details and usage example).
     3  /*
     4   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     5   */
     6  package transport_test
     7  
     8  import (
     9  	"fmt"
    10  	"io"
    11  	"net/http/httptest"
    12  	"strconv"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/NVIDIA/aistore/api/apc"
    17  	"github.com/NVIDIA/aistore/cmn"
    18  	"github.com/NVIDIA/aistore/cmn/atomic"
    19  	"github.com/NVIDIA/aistore/cmn/cos"
    20  	"github.com/NVIDIA/aistore/cmn/mono"
    21  	"github.com/NVIDIA/aistore/core"
    22  	"github.com/NVIDIA/aistore/core/meta"
    23  	"github.com/NVIDIA/aistore/core/mock"
    24  	"github.com/NVIDIA/aistore/memsys"
    25  	"github.com/NVIDIA/aistore/tools/tassert"
    26  	"github.com/NVIDIA/aistore/tools/tlog"
    27  	"github.com/NVIDIA/aistore/transport"
    28  	"github.com/NVIDIA/aistore/transport/bundle"
    29  )
    30  
    31  type (
    32  	sowner     struct{}
    33  	slisteners struct{}
    34  )
    35  
    36  var (
    37  	smap      meta.Smap
    38  	listeners slisteners
    39  )
    40  
    41  func (*sowner) Get() *meta.Smap               { return &smap }
    42  func (*sowner) Listeners() meta.SmapListeners { return &listeners }
    43  
    44  func (*slisteners) Reg(meta.Slistener)   {}
    45  func (*slisteners) Unreg(meta.Slistener) {}
    46  
    47  func TestBundle(t *testing.T) {
    48  	tests := []struct {
    49  		name string
    50  		nvs  cos.StrKVs
    51  	}{
    52  		{
    53  			name: "not-compressed",
    54  			nvs: cos.StrKVs{
    55  				"compression": apc.CompressNever,
    56  			},
    57  		},
    58  		{
    59  			name: "not-compressed-unsized",
    60  			nvs: cos.StrKVs{
    61  				"compression": apc.CompressNever,
    62  				"unsized":     "yes",
    63  			},
    64  		},
    65  	}
    66  	if !testing.Short() {
    67  		testsLong := []struct {
    68  			name string
    69  			nvs  cos.StrKVs
    70  		}{
    71  			{
    72  				name: "compress-block-1M",
    73  				nvs: cos.StrKVs{
    74  					"compression": apc.CompressAlways,
    75  					"block":       "1MiB",
    76  				},
    77  			},
    78  			{
    79  				name: "compress-block-256K",
    80  				nvs: cos.StrKVs{
    81  					"compression": apc.CompressAlways,
    82  					"block":       "256KiB",
    83  				},
    84  			},
    85  			{
    86  				name: "compress-block-256K-unsized",
    87  				nvs: cos.StrKVs{
    88  					"compression": apc.CompressAlways,
    89  					"block":       "256KiB",
    90  					"unsized":     "yes",
    91  				},
    92  			},
    93  		}
    94  		tests = append(tests, testsLong...)
    95  	}
    96  
    97  	tMock := mock.NewTarget(nil)
    98  	tMock.SO = &sowner{}
    99  	core.T = tMock
   100  
   101  	for _, test := range tests {
   102  		t.Run(test.name, func(t *testing.T) {
   103  			testBundle(t, test.nvs)
   104  			time.Sleep(time.Second)
   105  		})
   106  	}
   107  }
   108  
   109  func testBundle(t *testing.T, nvs cos.StrKVs) {
   110  	var (
   111  		numCompleted atomic.Int64
   112  		mmsa, _      = memsys.NewMMSA("bundle.test", false)
   113  		network      = cmn.NetIntraData
   114  		trname       = "bundle" + nvs["block"]
   115  		tss          = make([]*httptest.Server, 0, 32)
   116  	)
   117  
   118  	// init local target
   119  	tMock := mock.NewTarget(nil)
   120  	tMock.SO = &sowner{}
   121  	core.T = tMock
   122  	lsnode := tMock.Snode()
   123  
   124  	// add target nodes
   125  	smap.Tmap = make(meta.NodeMap, 100)
   126  	smap.Tmap[lsnode.ID()] = lsnode
   127  	for i := range 10 {
   128  		ts := httptest.NewServer(objmux)
   129  		tss = append(tss, ts)
   130  		addTarget(&smap, ts, i)
   131  	}
   132  	defer func() {
   133  		for _, ts := range tss {
   134  			ts.Close()
   135  		}
   136  		mmsa.Terminate(false)
   137  	}()
   138  	smap.Version = 1
   139  
   140  	receive := func(hdr *transport.ObjHdr, objReader io.Reader, err error) error {
   141  		if err != nil && !cos.IsEOF(err) {
   142  			tassert.CheckFatal(t, err)
   143  		}
   144  		written, _ := io.Copy(io.Discard, objReader)
   145  		cos.Assert(written == hdr.ObjAttrs.Size || hdr.IsUnsized())
   146  		return nil
   147  	}
   148  	callback := func(*transport.ObjHdr, io.ReadCloser, any, error) {
   149  		numCompleted.Inc()
   150  	}
   151  
   152  	err := transport.Handle(trname, receive) // URL = /v1/transport/10G
   153  	tassert.CheckFatal(t, err)
   154  	defer transport.Unhandle(trname)
   155  
   156  	var (
   157  		config         = cmn.GCO.Get()
   158  		httpclient     = transport.NewIntraDataClient()
   159  		random         = newRand(mono.NanoTime())
   160  		wbuf, slab     = mmsa.Alloc()
   161  		extra          = &transport.Extra{Compression: nvs["compression"]}
   162  		size, prevsize int64
   163  		multiplier     = int(random.Int63()%13) + 4
   164  		num            int
   165  		usePDU         bool
   166  	)
   167  	if nvs["compression"] != apc.CompressNever {
   168  		v, _ := cos.ParseSize(nvs["block"], cos.UnitsIEC)
   169  		cos.Assert(v == cos.MiB*4 || v == cos.MiB || v == cos.KiB*256 || v == cos.KiB*64)
   170  		config = cmn.GCO.BeginUpdate()
   171  		config.Transport.LZ4BlockMaxSize = cos.SizeIEC(v)
   172  		cmn.GCO.CommitUpdate(config)
   173  		if err := config.Transport.Validate(); err != nil {
   174  			tassert.CheckFatal(t, err)
   175  		}
   176  	}
   177  	if _, usePDU = nvs["unsized"]; usePDU {
   178  		extra.SizePDU = memsys.DefaultBufSize
   179  	}
   180  	extra.Config = config
   181  	_, _ = random.Read(wbuf)
   182  	sb := bundle.New(httpclient,
   183  		bundle.Args{Net: network, Trname: trname, Multiplier: multiplier, Extra: extra})
   184  	var numGs int64 = 6
   185  	if testing.Short() {
   186  		numGs = 1
   187  	}
   188  	for size < cos.GiB*numGs {
   189  		var err error
   190  		hdr := genRandomHeader(random, usePDU)
   191  		objSize := hdr.ObjAttrs.Size
   192  		if num%7 == 0 {
   193  			objSize, hdr.ObjAttrs.Size = 0, 0
   194  			err = sb.Send(&transport.Obj{Hdr: hdr, Callback: callback}, nil)
   195  		} else {
   196  			reader := &randReader{buf: wbuf, hdr: hdr, slab: slab, clone: true} // FIXME: multiplier reopen
   197  			if hdr.IsUnsized() {
   198  				reader.offEOF = int64(random.Int31()>>1) + 1
   199  				objSize = reader.offEOF
   200  			}
   201  			err = sb.Send(&transport.Obj{Hdr: hdr, Callback: callback}, reader)
   202  		}
   203  		if err != nil {
   204  			t.Fatalf("%s: exiting with err [%v]\n", sb, err)
   205  		}
   206  		num++
   207  		size += objSize
   208  		if size-prevsize >= cos.GiB {
   209  			tlog.Logf("%s: %d GiB\n", sb, size/cos.GiB)
   210  			prevsize = size
   211  		}
   212  	}
   213  	sb.Close(true /* gracefully */)
   214  	stats := sb.GetStats()
   215  
   216  	slab.Free(wbuf)
   217  
   218  	if nvs["compression"] != apc.CompressNever {
   219  		for id, tstat := range stats {
   220  			fmt.Printf("send$ %s/%s: offset=%d, num=%d(%d), compression-ratio=%.2f\n",
   221  				id, trname, tstat.Offset.Load(), tstat.Num.Load(), num, tstat.CompressionRatio())
   222  		}
   223  	} else {
   224  		for id, tstat := range stats {
   225  			fmt.Printf("send$ %s/%s: offset=%d, num=%d(%d)\n",
   226  				id, trname, tstat.Offset.Load(), tstat.Num.Load(), num)
   227  		}
   228  	}
   229  	fmt.Printf("send$: num-sent=%d, num-completed=%d\n", num, numCompleted.Load())
   230  }
   231  
   232  func addTarget(smap *meta.Smap, ts *httptest.Server, i int) {
   233  	netinfo := meta.NetInfo{URL: ts.URL}
   234  	tid := "t_" + strconv.FormatInt(int64(i), 10)
   235  	smap.Tmap[tid] = &meta.Snode{PubNet: netinfo, ControlNet: netinfo, DataNet: netinfo}
   236  }