github.com/weaviate/weaviate@v1.24.6/usecases/cluster/delegate_test.go (about)

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  package cluster
    13  
    14  import (
    15  	"fmt"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/hashicorp/memberlist"
    20  	"github.com/pkg/errors"
    21  	"github.com/sirupsen/logrus/hooks/test"
    22  	"github.com/stretchr/testify/assert"
    23  )
    24  
    25  func TestDiskSpaceMarshal(t *testing.T) {
    26  	for _, name := range []string{"", "host-12:1", "2", "00", "-jhd"} {
    27  		want := spaceMsg{
    28  			header{
    29  				ProtoVersion: uint8(1),
    30  				OpCode:       _OpCode(2),
    31  			},
    32  			DiskUsage{
    33  				Total:     256,
    34  				Available: 3,
    35  			},
    36  			uint8(len(name)),
    37  			name,
    38  		}
    39  		bytes, err := want.marshal()
    40  		assert.Nil(t, err)
    41  		got := spaceMsg{}
    42  		err = got.unmarshal(bytes)
    43  		assert.Nil(t, err)
    44  		assert.Equal(t, want, got)
    45  	}
    46  
    47  	// simulate old version
    48  	x := spaceMsg{
    49  		header{
    50  			ProtoVersion: uint8(1),
    51  			OpCode:       _OpCode(2),
    52  		},
    53  		DiskUsage{
    54  			Total:     256,
    55  			Available: 3,
    56  		},
    57  		uint8('0'),
    58  		"123",
    59  	}
    60  	bytes, err := x.marshal()
    61  	want := x
    62  	want.NodeLen = 4
    63  	want.Node = "0123"
    64  	assert.Nil(t, err)
    65  	got := spaceMsg{}
    66  	err = got.unmarshal(bytes)
    67  	assert.Nil(t, err)
    68  	assert.Equal(t, want, got)
    69  }
    70  
    71  func TestDelegateGetSet(t *testing.T) {
    72  	logger, _ := test.NewNullLogger()
    73  	now := time.Now().UnixMilli() - 1
    74  	st := State{
    75  		delegate: delegate{
    76  			Name:     "ABC",
    77  			dataPath: ".",
    78  			log:      logger,
    79  			Cache:    make(map[string]NodeInfo, 32),
    80  		},
    81  	}
    82  	st.delegate.NotifyMsg(nil)
    83  	st.delegate.GetBroadcasts(0, 0)
    84  	st.delegate.NodeMeta(0)
    85  	spaces := make([]spaceMsg, 32)
    86  	for i := range spaces {
    87  		node := fmt.Sprintf("N-%d", i+1)
    88  		spaces[i] = spaceMsg{
    89  			header: header{
    90  				OpCode:       _OpCodeDisk,
    91  				ProtoVersion: _ProtoVersion + 2,
    92  			},
    93  			DiskUsage: DiskUsage{
    94  				uint64(i + 1),
    95  				uint64(i),
    96  			},
    97  			Node:    node,
    98  			NodeLen: uint8(len(node)),
    99  		}
   100  	}
   101  
   102  	done := make(chan struct{})
   103  	go func() {
   104  		for _, x := range spaces {
   105  			bytes, _ := x.marshal()
   106  			st.delegate.MergeRemoteState(bytes, false)
   107  		}
   108  		done <- struct{}{}
   109  	}()
   110  
   111  	_, ok := st.delegate.get("X")
   112  	assert.False(t, ok)
   113  
   114  	for _, x := range spaces {
   115  		space, ok := st.NodeInfo(x.Node)
   116  		if ok {
   117  			assert.Equal(t, x.DiskUsage, space.DiskUsage)
   118  		}
   119  	}
   120  	<-done
   121  	for _, x := range spaces {
   122  		info, ok := st.NodeInfo(x.Node)
   123  		assert.Greater(t, info.LastTimeMilli, now)
   124  		want := NodeInfo{x.DiskUsage, info.LastTimeMilli}
   125  		assert.Equal(t, want, info)
   126  		assert.True(t, ok)
   127  		st.delegate.delete(x.Node)
   128  
   129  	}
   130  	assert.Empty(t, st.delegate.Cache)
   131  	st.delegate.init(diskSpace)
   132  	assert.Equal(t, 1, len(st.delegate.Cache))
   133  
   134  	st.delegate.MergeRemoteState(st.delegate.LocalState(false), false)
   135  	space, ok := st.NodeInfo(st.delegate.Name)
   136  	assert.True(t, ok)
   137  	assert.Greater(t, space.Total, space.Available)
   138  }
   139  
   140  func TestDelegateMergeRemoteState(t *testing.T) {
   141  	logger, _ := test.NewNullLogger()
   142  	var (
   143  		node = "N1"
   144  		d    = delegate{
   145  			Name:     node,
   146  			dataPath: ".",
   147  			log:      logger,
   148  			Cache:    make(map[string]NodeInfo, 32),
   149  		}
   150  		x = spaceMsg{
   151  			header{
   152  				OpCode:       _OpCodeDisk,
   153  				ProtoVersion: _ProtoVersion,
   154  			},
   155  			DiskUsage{2, 1},
   156  			uint8(len(node)),
   157  			node,
   158  		}
   159  	)
   160  	// valid operation payload
   161  	bytes, err := x.marshal()
   162  	assert.Nil(t, err)
   163  	d.MergeRemoteState(bytes, false)
   164  	_, ok := d.get(node)
   165  	assert.True(t, ok)
   166  
   167  	node = "N2"
   168  	// invalid payload => expect marshalling error
   169  	d.MergeRemoteState(bytes[:4], false)
   170  	assert.Nil(t, err)
   171  	_, ok = d.get(node)
   172  	assert.False(t, ok)
   173  
   174  	// valid payload but operation is not supported
   175  	node = "N2"
   176  	x.header.OpCode = _OpCodeDisk + 2
   177  	bytes, err = x.marshal()
   178  	d.MergeRemoteState(bytes, false)
   179  	assert.Nil(t, err)
   180  	_, ok = d.get(node)
   181  	assert.False(t, ok)
   182  }
   183  
   184  func TestDelegateSort(t *testing.T) {
   185  	now := time.Now().UnixMilli()
   186  	GB := uint64(1) << 30
   187  	delegate := delegate{
   188  		Name:     "ABC",
   189  		dataPath: ".",
   190  		Cache:    make(map[string]NodeInfo, 32),
   191  	}
   192  
   193  	delegate.set("N1", NodeInfo{DiskUsage{Available: GB}, now})
   194  	delegate.set("N2", NodeInfo{DiskUsage{Available: 3 * GB}, now})
   195  	delegate.set("N3", NodeInfo{DiskUsage{Available: 2 * GB}, now})
   196  	delegate.set("N4", NodeInfo{DiskUsage{Available: 4 * GB}, now})
   197  	got := delegate.sortCandidates([]string{"N1", "N0", "N2", "N4", "N3"})
   198  	assert.Equal(t, []string{"N4", "N2", "N3", "N1", "N0"}, got)
   199  
   200  	delegate.set("N1", NodeInfo{DiskUsage{Available: GB - 10}, now})
   201  	// insert equivalent nodes "N2" and "N3"
   202  	delegate.set("N2", NodeInfo{DiskUsage{Available: GB + 128}, now})
   203  	delegate.set("N3", NodeInfo{DiskUsage{Available: GB + 512}, now})
   204  	// one block more
   205  	delegate.set("N4", NodeInfo{DiskUsage{Available: GB + 1<<25}, now})
   206  	got = delegate.sortCandidates([]string{"N1", "N0", "N2", "N3", "N4"})
   207  	if got[1] == "N2" {
   208  		assert.Equal(t, []string{"N4", "N2", "N3", "N1", "N0"}, got)
   209  	} else {
   210  		assert.Equal(t, []string{"N4", "N3", "N2", "N1", "N0"}, got)
   211  	}
   212  }
   213  
   214  func TestDelegateCleanUp(t *testing.T) {
   215  	st := State{
   216  		delegate: delegate{
   217  			Name:     "N0",
   218  			dataPath: ".",
   219  		},
   220  	}
   221  	diskSpace := func(path string) (DiskUsage, error) {
   222  		return DiskUsage{100, 50}, nil
   223  	}
   224  	st.delegate.init(diskSpace)
   225  	_, ok := st.delegate.get("N0")
   226  	assert.True(t, ok, "N0 must exist")
   227  	st.delegate.set("N1", NodeInfo{LastTimeMilli: 1})
   228  	st.delegate.set("N2", NodeInfo{LastTimeMilli: 2})
   229  	handler := events{&st.delegate}
   230  	handler.NotifyJoin(nil)
   231  	handler.NotifyUpdate(nil)
   232  	handler.NotifyLeave(&memberlist.Node{Name: "N0"})
   233  	handler.NotifyLeave(&memberlist.Node{Name: "N1"})
   234  	handler.NotifyLeave(&memberlist.Node{Name: "N2"})
   235  	assert.Empty(t, st.delegate.Cache)
   236  }
   237  
   238  func TestDelegateLocalState(t *testing.T) {
   239  	now := time.Now().UnixMilli() - 1
   240  	errAny := errors.New("any error")
   241  	logger, _ := test.NewNullLogger()
   242  
   243  	t.Run("FirstError", func(t *testing.T) {
   244  		d := delegate{
   245  			Name:     "N0",
   246  			dataPath: ".",
   247  			log:      logger,
   248  			Cache:    map[string]NodeInfo{},
   249  		}
   250  		du := func(path string) (DiskUsage, error) { return DiskUsage{}, errAny }
   251  		d.init(du)
   252  
   253  		// error reading disk space
   254  		d.LocalState(true)
   255  		assert.Len(t, d.Cache, 1)
   256  	})
   257  
   258  	t.Run("Success", func(t *testing.T) {
   259  		d := delegate{
   260  			Name:     "N0",
   261  			dataPath: ".",
   262  			log:      logger,
   263  			Cache:    map[string]NodeInfo{},
   264  		}
   265  		du := func(path string) (DiskUsage, error) { return DiskUsage{5, 1}, nil }
   266  		d.init(du)
   267  		// successful case
   268  		d.LocalState(true)
   269  		got, ok := d.get("N0")
   270  		assert.True(t, ok)
   271  		assert.Greater(t, got.LastTimeMilli, now)
   272  		assert.Equal(t, DiskUsage{5, 1}, got.DiskUsage)
   273  	})
   274  }
   275  
   276  func TestDelegateUpdater(t *testing.T) {
   277  	logger, _ := test.NewNullLogger()
   278  	now := time.Now().UnixMilli() - 1
   279  
   280  	d := delegate{
   281  		Name:     "N0",
   282  		dataPath: ".",
   283  		log:      logger,
   284  		Cache:    map[string]NodeInfo{},
   285  	}
   286  	err := d.init(nil)
   287  	assert.NotNil(t, err)
   288  	doneCh := make(chan bool)
   289  	nCalls := uint64(0)
   290  	du := func(path string) (DiskUsage, error) {
   291  		nCalls++
   292  		if nCalls == 1 || nCalls == 3 {
   293  			return DiskUsage{2 * nCalls, nCalls}, nil
   294  		}
   295  		if nCalls == 2 {
   296  			return DiskUsage{}, fmt.Errorf("any")
   297  		}
   298  		if nCalls == 4 {
   299  			close(doneCh)
   300  		}
   301  		return DiskUsage{}, fmt.Errorf("any")
   302  	}
   303  	go d.updater(time.Millisecond, 5*time.Millisecond, du)
   304  
   305  	<-doneCh
   306  
   307  	// error reading disk space
   308  	d.LocalState(true)
   309  	got, ok := d.get("N0")
   310  	assert.True(t, ok)
   311  	assert.Greater(t, got.LastTimeMilli, now)
   312  	assert.Equal(t, DiskUsage{3 * 2, 3}, got.DiskUsage)
   313  }