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 }