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 }