github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/tools/client.go (about) 1 // Package tools provides common tools and utilities for all unit and integration tests 2 /* 3 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package tools 6 7 import ( 8 "errors" 9 "fmt" 10 "math/rand" 11 "net/http" 12 "path" 13 "strconv" 14 "sync" 15 "testing" 16 "time" 17 18 "github.com/NVIDIA/aistore/api" 19 "github.com/NVIDIA/aistore/api/apc" 20 "github.com/NVIDIA/aistore/cmn" 21 "github.com/NVIDIA/aistore/cmn/atomic" 22 "github.com/NVIDIA/aistore/cmn/cos" 23 "github.com/NVIDIA/aistore/core/meta" 24 "github.com/NVIDIA/aistore/stats" 25 "github.com/NVIDIA/aistore/tools/readers" 26 "github.com/NVIDIA/aistore/tools/tassert" 27 "github.com/NVIDIA/aistore/tools/tlog" 28 "github.com/NVIDIA/aistore/tools/trand" 29 "github.com/NVIDIA/aistore/xact" 30 "golang.org/x/sync/errgroup" 31 ) 32 33 const ( 34 // This value is holds the input of 'proxyURLFlag' from init_tests.go. 35 // It is used in BaseAPIParams to determine if the cluster is running 36 // on a 37 // 1. local instance (no docker) - works 38 // 2. local docker instance - works 39 // 3. AWS-deployed cluster - not tested (but runs mainly with Ansible) 40 MockDaemonID = "MOCK" 41 ) 42 43 // times and timeouts 44 const ( 45 WaitClusterStartup = 20 * time.Second 46 RebalanceStartTimeout = 10 * time.Second 47 MaxCplaneTimeout = 10 * time.Second 48 49 CopyBucketTimeout = 3 * time.Minute 50 MultiProxyTestTimeout = 3 * time.Minute 51 52 DsortFinishTimeout = 6 * time.Minute 53 RebalanceTimeout = 2 * time.Minute 54 EvictPrefetchTimeout = 2 * time.Minute 55 BucketCleanupTimeout = time.Minute 56 57 xactPollSleep = time.Second 58 controlPlaneSleep = 2 * time.Second 59 ) 60 61 type PutObjectsArgs struct { 62 ProxyURL string 63 Bck cmn.Bck 64 ObjPath string 65 CksumType string 66 ObjSize uint64 67 ObjCnt int 68 WorkerCnt int 69 FixedSize bool 70 Ordered bool // true - object names make sequence, false - names are random 71 IgnoreErr bool 72 } 73 74 func Del(proxyURL string, bck cmn.Bck, object string, wg *sync.WaitGroup, errCh chan error, silent bool) error { 75 if wg != nil { 76 defer wg.Done() 77 } 78 if !silent { 79 fmt.Printf("DEL: %s\n", object) 80 } 81 bp := BaseAPIParams(proxyURL) 82 err := api.DeleteObject(bp, bck, object) 83 if err != nil && errCh != nil { 84 errCh <- err 85 } 86 return err 87 } 88 89 func CheckObjIsPresent(proxyURL string, bck cmn.Bck, objName string) bool { 90 bp := BaseAPIParams(proxyURL) 91 _, err := api.HeadObject(bp, bck, objName, apc.FltPresent, true /*silent*/) 92 return err == nil 93 } 94 95 // Put sends a PUT request to the given URL 96 func Put(proxyURL string, bck cmn.Bck, objName string, reader readers.Reader, errCh chan error) { 97 bp := BaseAPIParams(proxyURL) 98 putArgs := api.PutArgs{ 99 BaseParams: bp, 100 Bck: bck, 101 ObjName: objName, 102 Cksum: reader.Cksum(), 103 Reader: reader, 104 } 105 _, err := api.PutObject(&putArgs) 106 if err == nil { 107 return 108 } 109 if errCh == nil { 110 fmt.Printf("Failed to PUT %s: %v (nil error channel)\n", bck.Cname(objName), err) 111 } else { 112 errCh <- err 113 } 114 } 115 116 // PutObject sends a PUT request to the given URL. 117 func PutObject(t *testing.T, bck cmn.Bck, objName string, reader readers.Reader) { 118 var ( 119 proxyURL = RandomProxyURL() 120 errCh = make(chan error, 1) 121 ) 122 Put(proxyURL, bck, objName, reader, errCh) 123 tassert.SelectErr(t, errCh, "put", true) 124 } 125 126 // ListObjectNames returns a slice of object names of all objects that match the prefix in a bucket 127 func ListObjectNames(proxyURL string, bck cmn.Bck, prefix string, objectCountLimit int64, cached bool) ([]string, error) { 128 var ( 129 bp = BaseAPIParams(proxyURL) 130 msg = &apc.LsoMsg{Prefix: prefix} 131 ) 132 if cached { 133 msg.Flags = apc.LsObjCached 134 } 135 data, err := api.ListObjects(bp, bck, msg, api.ListArgs{Limit: objectCountLimit}) 136 if err != nil { 137 return nil, err 138 } 139 140 objs := make([]string, 0, len(data.Entries)) 141 for _, obj := range data.Entries { 142 objs = append(objs, obj.Name) 143 } 144 return objs, nil 145 } 146 147 func GetPrimaryURL() string { 148 primary, err := GetPrimaryProxy(proxyURLReadOnly) 149 if err == nil { 150 return primary.URL(cmn.NetPublic) 151 } 152 fmt.Printf("Warning: GetPrimaryProxy [%v] - retrying once...\n", err) 153 if currSmap == nil { 154 time.Sleep(time.Second) 155 primary, err = GetPrimaryProxy(proxyURLReadOnly) 156 } else if proxyURL := currSmap.Primary.URL(cmn.NetPublic); proxyURL != proxyURLReadOnly { 157 primary, err = GetPrimaryProxy(proxyURL) 158 } else { 159 var psi *meta.Snode 160 if psi, err = currSmap.GetRandProxy(true /*exclude primary*/); err == nil { 161 primary, err = GetPrimaryProxy(psi.URL(cmn.NetPublic)) 162 } 163 } 164 if err != nil { 165 fmt.Printf("Warning: GetPrimaryProxy [%v] - returning global %q\n", err, proxyURLReadOnly) 166 return proxyURLReadOnly 167 } 168 return primary.URL(cmn.NetPublic) 169 } 170 171 // GetPrimaryProxy returns the primary proxy 172 func GetPrimaryProxy(proxyURL string) (*meta.Snode, error) { 173 bp := BaseAPIParams(proxyURL) 174 smap, err := api.GetClusterMap(bp) 175 if err != nil { 176 return nil, err 177 } 178 if currSmap == nil || currSmap.Version < smap.Version { 179 currSmap = smap 180 } 181 return smap.Primary, err 182 } 183 184 func GetProxyReadiness(proxyURL string) error { 185 return api.GetProxyReadiness(BaseAPIParams(proxyURL)) 186 } 187 188 func CreateBucket(tb testing.TB, proxyURL string, bck cmn.Bck, props *cmn.BpropsToSet, cleanup bool) { 189 bp := BaseAPIParams(proxyURL) 190 err := api.CreateBucket(bp, bck, props) 191 tassert.CheckFatal(tb, err) 192 if cleanup { 193 tb.Cleanup(func() { 194 DestroyBucket(tb, proxyURL, bck) 195 }) 196 } 197 } 198 199 // is usually called to cleanup (via tb.Cleanup) 200 func DestroyBucket(tb testing.TB, proxyURL string, bck cmn.Bck) { 201 bp := BaseAPIParams(proxyURL) 202 exists, err := api.QueryBuckets(bp, cmn.QueryBcks(bck), apc.FltExists) 203 tassert.CheckFatal(tb, err) 204 if exists { 205 err = api.DestroyBucket(bp, bck) 206 if err == nil { 207 return 208 } 209 herr := cmn.Err2HTTPErr(err) 210 if herr == nil || herr.Status != http.StatusNotFound { 211 tassert.CheckFatal(tb, err) 212 } 213 } 214 } 215 216 func EvictRemoteBucket(tb testing.TB, proxyURL string, bck cmn.Bck) { 217 if backend := bck.Backend(); backend != nil { 218 bck.Copy(backend) 219 } 220 err := api.EvictRemoteBucket(BaseAPIParams(proxyURL), bck, false) 221 tassert.CheckFatal(tb, err) 222 } 223 224 func CleanupRemoteBucket(t *testing.T, proxyURL string, bck cmn.Bck, prefix string) { 225 if !bck.IsRemote() { 226 return 227 } 228 229 toDelete, err := ListObjectNames(proxyURL, bck, prefix, 0, false /*cached*/) 230 tassert.CheckFatal(t, err) 231 defer EvictRemoteBucket(t, proxyURL, bck) 232 233 if len(toDelete) == 0 { 234 return 235 } 236 237 bp := BaseAPIParams(proxyURL) 238 xid, err := api.DeleteMultiObj(bp, bck, toDelete, "" /*template*/) 239 tassert.CheckFatal(t, err) 240 args := xact.ArgsMsg{ID: xid, Kind: apc.ActDeleteObjects, Timeout: BucketCleanupTimeout} 241 _, err = api.WaitForXactionIC(bp, &args) 242 tassert.CheckFatal(t, err) 243 } 244 245 func SetBackendBck(t *testing.T, bp api.BaseParams, srcBck, dstBck cmn.Bck) { 246 // find out real provider of the bucket 247 p, err := api.HeadBucket(bp, dstBck, false /* don't add to cluster MD */) 248 tassert.CheckFatal(t, err) 249 250 _, err = api.SetBucketProps(bp, srcBck, &cmn.BpropsToSet{ 251 BackendBck: &cmn.BackendBckToSet{ 252 Name: apc.Ptr(dstBck.Name), 253 Provider: apc.Ptr(p.Provider), 254 }, 255 }) 256 tassert.CheckFatal(t, err) 257 } 258 259 func RmTargetSkipRebWait(t *testing.T, proxyURL string, smap *meta.Smap) (*meta.Smap, *meta.Snode) { 260 var ( 261 removeTarget, _ = smap.GetRandTarget() 262 origTgtCnt = smap.CountActiveTs() 263 args = &apc.ActValRmNode{DaemonID: removeTarget.ID(), SkipRebalance: true} 264 ) 265 _, err := api.StartMaintenance(BaseAPIParams(proxyURL), args) 266 tassert.CheckFatal(t, err) 267 newSmap, err := WaitForClusterState( 268 proxyURL, 269 "target is gone", 270 smap.Version, 271 smap.CountActivePs(), 272 origTgtCnt-1, 273 ) 274 tassert.CheckFatal(t, err) 275 newTgtCnt := newSmap.CountActiveTs() 276 tassert.Fatalf(t, newTgtCnt == origTgtCnt-1, 277 "new smap expected to have 1 target less: %d (v%d) vs %d (v%d)", newTgtCnt, origTgtCnt, 278 newSmap.Version, smap.Version) 279 return newSmap, removeTarget 280 } 281 282 // Internal API to remove a node from Smap: use it to unregister MOCK targets/proxies. 283 // Use `JoinCluster` to attach node back. 284 func RemoveNodeUnsafe(proxyURL, sid string) error { 285 return _removeNodeFromSmap(proxyURL, sid, MaxCplaneTimeout) 286 } 287 288 func WaitForObjectToBeDowloaded(bp api.BaseParams, bck cmn.Bck, objName string, timeout time.Duration) error { 289 maxTime := time.Now().Add(timeout) 290 for { 291 if time.Now().After(maxTime) { 292 return fmt.Errorf("timed out (%v) waiting for %s download", timeout, bck.Cname(objName)) 293 } 294 reslist, err := api.ListObjects(bp, bck, &apc.LsoMsg{}, api.ListArgs{}) 295 if err != nil { 296 return err 297 } 298 for _, obj := range reslist.Entries { 299 if obj.Name == objName { 300 return nil 301 } 302 } 303 time.Sleep(500 * time.Millisecond) 304 } 305 } 306 307 func EnsureObjectsExist(t *testing.T, params api.BaseParams, bck cmn.Bck, objectsNames ...string) { 308 for _, objName := range objectsNames { 309 _, err := api.GetObject(params, bck, objName, nil) 310 if err != nil { 311 t.Errorf("Unexpected GetObject(%s) error: %v.", objName, err) 312 } 313 } 314 } 315 316 //nolint:gocritic // need a copy of PutObjectsArgs 317 func PutRandObjs(args PutObjectsArgs) ([]string, int, error) { 318 var ( 319 errCnt = atomic.NewInt32(0) 320 putCnt = atomic.NewInt32(0) 321 322 workerCnt = 40 // Default worker count. 323 group = &errgroup.Group{} 324 objNames = make([]string, 0, args.ObjCnt) 325 bp = BaseAPIParams(args.ProxyURL) 326 ) 327 328 if args.WorkerCnt > 0 { 329 workerCnt = args.WorkerCnt 330 } 331 workerCnt = min(workerCnt, args.ObjCnt) 332 333 for i := range args.ObjCnt { 334 if args.Ordered { 335 objNames = append(objNames, path.Join(args.ObjPath, strconv.Itoa(i))) 336 } else { 337 objNames = append(objNames, path.Join(args.ObjPath, trand.String(16))) 338 } 339 } 340 chunkSize := (len(objNames) + workerCnt - 1) / workerCnt 341 for i := 0; i < len(objNames); i += chunkSize { 342 group.Go(func(start, end int) func() error { 343 return func() error { 344 for _, objName := range objNames[start:end] { 345 size := args.ObjSize 346 if size == 0 { // Size not specified so generate something. 347 size = uint64(cos.NowRand().Intn(cos.KiB)+1) * cos.KiB 348 } else if !args.FixedSize { // Randomize object size. 349 size += uint64(rand.Int63n(cos.KiB)) 350 } 351 352 if args.CksumType == "" { 353 args.CksumType = cos.ChecksumNone 354 } 355 356 reader, err := readers.NewRand(int64(size), args.CksumType) 357 cos.AssertNoErr(err) 358 359 // We could PUT while creating files, but that makes it 360 // begin all the puts immediately (because creating random files is fast 361 // compared to the list objects call that getRandomFiles does) 362 _, err = api.PutObject(&api.PutArgs{ 363 BaseParams: bp, 364 Bck: args.Bck, 365 ObjName: objName, 366 Cksum: reader.Cksum(), 367 Reader: reader, 368 Size: size, 369 SkipVC: true, 370 }) 371 putCnt.Inc() 372 if err != nil { 373 if args.IgnoreErr { 374 errCnt.Inc() 375 return nil 376 } 377 return err 378 } 379 } 380 return nil 381 } 382 }(i, min(i+chunkSize, len(objNames)))) 383 } 384 385 err := group.Wait() 386 cos.Assert(err != nil || len(objNames) == int(putCnt.Load())) 387 return objNames, int(errCnt.Load()), err 388 } 389 390 // Put an object into a cloud bucket and evict it afterwards - can be used to test cold GET 391 func PutObjectInRemoteBucketWithoutCachingLocally(t *testing.T, bck cmn.Bck, object string, objContent cos.ReadOpenCloser) { 392 bp := BaseAPIParams() 393 394 _, err := api.PutObject(&api.PutArgs{ 395 BaseParams: bp, 396 Bck: bck, 397 ObjName: object, 398 Reader: objContent, 399 }) 400 tassert.CheckFatal(t, err) 401 402 err = api.EvictObject(bp, bck, object) 403 tassert.CheckFatal(t, err) 404 } 405 406 func GetObjectAtime(t *testing.T, bp api.BaseParams, bck cmn.Bck, object, timeFormat string) (time.Time, string) { 407 msg := &apc.LsoMsg{Props: apc.GetPropsAtime, TimeFormat: timeFormat, Prefix: object} 408 bucketList, err := api.ListObjects(bp, bck, msg, api.ListArgs{}) 409 tassert.CheckFatal(t, err) 410 411 for _, entry := range bucketList.Entries { 412 if entry.Name == object { 413 atime, err := time.Parse(timeFormat, entry.Atime) 414 tassert.CheckFatal(t, err) 415 return atime, entry.Atime 416 } 417 } 418 419 tassert.Fatalf(t, false, "Cannot find %s in bucket %s", object, bck) 420 return time.Time{}, "" 421 } 422 423 // WaitForDsortToFinish waits until all dSorts jobs finished without failure or 424 // all jobs abort. 425 func WaitForDsortToFinish(proxyURL, managerUUID string) (allAborted bool, err error) { 426 tlog.Logf("waiting for dsort[%s]\n", managerUUID) 427 428 bp := BaseAPIParams(proxyURL) 429 deadline := time.Now().Add(DsortFinishTimeout) 430 for time.Now().Before(deadline) { 431 all, err := api.MetricsDsort(bp, managerUUID) 432 if err != nil { 433 return false, err 434 } 435 436 allAborted := true 437 allFinished := true 438 for _, jmetrics := range all { 439 m := jmetrics.Metrics 440 allAborted = allAborted && m.Aborted.Load() 441 allFinished = allFinished && 442 !m.Aborted.Load() && 443 m.Extraction.Finished && 444 m.Sorting.Finished && 445 m.Creation.Finished 446 } 447 if allAborted { 448 return true, nil 449 } 450 if allFinished { 451 return false, nil 452 } 453 time.Sleep(500 * time.Millisecond) 454 } 455 return false, errors.New("deadline exceeded") 456 } 457 458 func BaseAPIParams(urls ...string) api.BaseParams { 459 var u string 460 if len(urls) > 0 && urls[0] != "" { 461 u = urls[0] 462 } else { 463 u = RandomProxyURL() 464 } 465 return api.BaseParams{Client: gctx.Client, URL: u, Token: LoggedUserToken, UA: "tools/test"} 466 } 467 468 func EvictObjects(t *testing.T, proxyURL string, bck cmn.Bck, objList []string) { 469 bp := BaseAPIParams(proxyURL) 470 xid, err := api.EvictMultiObj(bp, bck, objList, "" /*template*/) 471 if err != nil { 472 t.Errorf("Evict bucket %s failed, err = %v", bck, err) 473 } 474 475 args := xact.ArgsMsg{ID: xid, Kind: apc.ActEvictObjects, Timeout: EvictPrefetchTimeout} 476 if _, err := api.WaitForXactionIC(bp, &args); err != nil { 477 t.Errorf("Wait for xaction to finish failed, err = %v", err) 478 } 479 } 480 481 // TODO -- FIXME: revise and rewrite 482 // 483 // Waits for both resilver and rebalance to complete. 484 // If they were not started, this function treats them as completed 485 // and returns. If timeout set, if any of rebalances doesn't complete before timeout 486 // the function ends with fatal. 487 func WaitForRebalAndResil(t testing.TB, bp api.BaseParams, timeouts ...time.Duration) { 488 var ( 489 wg = &sync.WaitGroup{} 490 errCh = make(chan error, 2) 491 ) 492 smap, err := api.GetClusterMap(bp) 493 tassert.CheckFatal(t, err) 494 495 if nat := smap.CountActiveTs(); nat < 1 { 496 // NOTE in re nat == 1: single remaining target vs. graceful shutdown and such 497 s := "No targets" 498 tlog.Logf("%s, %s - cannot rebalance\n", s, smap) 499 _waitResil(t, bp, controlPlaneSleep) 500 return 501 } 502 503 _waitReToStart(bp) 504 timeout := RebalanceTimeout 505 if len(timeouts) > 0 { 506 timeout = timeouts[0] 507 } 508 tlog.Logf("Waiting for rebalance and resilver to complete (timeout %v)\n", timeout) 509 wg.Add(2) 510 go func() { 511 defer wg.Done() 512 xargs := xact.ArgsMsg{Kind: apc.ActRebalance, OnlyRunning: true, Timeout: timeout} 513 if _, err := api.WaitForXactionIC(bp, &xargs); err != nil { 514 if cmn.IsStatusNotFound(err) { 515 return 516 } 517 errCh <- err 518 } 519 }() 520 521 go func() { 522 defer wg.Done() 523 xargs := xact.ArgsMsg{Kind: apc.ActResilver, OnlyRunning: true, Timeout: timeout} 524 if _, err := api.WaitForXactionIC(bp, &xargs); err != nil { 525 if cmn.IsStatusNotFound(err) { 526 return 527 } 528 errCh <- err 529 } 530 }() 531 532 wg.Wait() 533 close(errCh) 534 for err := range errCh { 535 tassert.CheckFatal(t, err) 536 return 537 } 538 } 539 540 // compare w/ `tools.WaitForResilvering` 541 func _waitResil(t testing.TB, bp api.BaseParams, timeout time.Duration) { 542 xargs := xact.ArgsMsg{Kind: apc.ActResilver, OnlyRunning: true, Timeout: timeout} 543 _, err := api.WaitForXactionIC(bp, &xargs) 544 if err == nil { 545 return 546 } 547 if herr, ok := err.(*cmn.ErrHTTP); ok { 548 if herr.Status == http.StatusNotFound { // double check iff not found 549 time.Sleep(xactPollSleep) 550 _, err = api.WaitForXactionIC(bp, &xargs) 551 } 552 } 553 if err == nil { 554 return 555 } 556 if herr, ok := err.(*cmn.ErrHTTP); ok { 557 if herr.Status == http.StatusNotFound { 558 err = nil 559 } 560 } 561 tassert.CheckError(t, err) 562 } 563 564 func WaitForRebalanceByID(t *testing.T, bp api.BaseParams, rebID string, timeouts ...time.Duration) { 565 if rebID == "" { 566 return 567 } 568 tassert.Fatalf(t, xact.IsValidRebID(rebID), "invalid reb ID %q", rebID) 569 timeout := RebalanceTimeout 570 if len(timeouts) > 0 { 571 timeout = timeouts[0] 572 } 573 tlog.Logf("Wait for rebalance %s\n", rebID) 574 xargs := xact.ArgsMsg{ID: rebID, Kind: apc.ActRebalance, OnlyRunning: true, Timeout: timeout} 575 _, err := api.WaitForXactionIC(bp, &xargs) 576 tassert.CheckFatal(t, err) 577 } 578 579 func _waitReToStart(bp api.BaseParams) { 580 var ( 581 kinds = []string{apc.ActRebalance, apc.ActResilver} 582 timeout = max(10*xactPollSleep, MaxCplaneTimeout) 583 retries = int(timeout / xactPollSleep) 584 ) 585 for range retries { 586 for _, kind := range kinds { 587 args := xact.ArgsMsg{Timeout: xactPollSleep, OnlyRunning: true, Kind: kind} 588 status, err := api.GetOneXactionStatus(bp, &args) 589 if err == nil { 590 if !status.Finished() { 591 return 592 } 593 } 594 } 595 time.Sleep(xactPollSleep) 596 } 597 tlog.Logf("Warning: timed out (%v) waiting for rebalance or resilver to start\n", timeout) 598 } 599 600 func GetClusterStats(t *testing.T, proxyURL string) stats.Cluster { 601 bp := BaseAPIParams(proxyURL) 602 scs, err := api.GetClusterStats(bp) 603 tassert.CheckFatal(t, err) 604 return scs 605 } 606 607 func GetNamedStatsVal(ds *stats.Node, name string) int64 { 608 v, ok := ds.Tracker[name] 609 if !ok { 610 return 0 611 } 612 return v.Value 613 } 614 615 func GetDaemonConfig(t *testing.T, node *meta.Snode) *cmn.Config { 616 bp := BaseAPIParams() 617 config, err := api.GetDaemonConfig(bp, node) 618 tassert.CheckFatal(t, err) 619 return config 620 } 621 622 func GetClusterMap(tb testing.TB, url string) *meta.Smap { 623 smap, err := waitForStartup(BaseAPIParams(url), tb) 624 if err == nil && (currSmap == nil || currSmap.Version < smap.Version) { 625 currSmap = smap 626 } 627 return smap 628 } 629 630 func getClusterConfig() (config *cmn.Config, err error) { 631 proxyURL := GetPrimaryURL() 632 primary, err := GetPrimaryProxy(proxyURL) 633 if err != nil { 634 return nil, err 635 } 636 return api.GetDaemonConfig(BaseAPIParams(proxyURL), primary) 637 } 638 639 func GetClusterConfig(t *testing.T) (config *cmn.Config) { 640 config, err := getClusterConfig() 641 tassert.CheckError(t, err) 642 return config 643 } 644 645 func SetClusterConfig(t *testing.T, nvs cos.StrKVs) { 646 proxyURL := GetPrimaryURL() 647 bp := BaseAPIParams(proxyURL) 648 err := api.SetClusterConfig(bp, nvs, false /*transient*/) 649 tassert.CheckError(t, err) 650 } 651 652 func SetClusterConfigUsingMsg(t *testing.T, toUpdate *cmn.ConfigToSet) { 653 proxyURL := GetPrimaryURL() 654 bp := BaseAPIParams(proxyURL) 655 err := api.SetClusterConfigUsingMsg(bp, toUpdate, false /*transient*/) 656 tassert.CheckFatal(t, err) 657 } 658 659 func CheckErrIsNotFound(t *testing.T, err error) { 660 if err == nil { 661 t.Fatalf("expected error") 662 return 663 } 664 herr, ok := err.(*cmn.ErrHTTP) 665 tassert.Fatalf(t, ok, "expected an error of the type *cmn.ErrHTTP, got %v(%T)", err, err) 666 tassert.Fatalf( 667 t, herr.Status == http.StatusNotFound, 668 "expected status: %d, got: %d.", http.StatusNotFound, herr.Status, 669 ) 670 } 671 672 func waitForStartup(bp api.BaseParams, ts ...testing.TB) (*meta.Smap, error) { 673 for { 674 smap, err := api.GetClusterMap(bp) 675 if err != nil { 676 if api.HTTPStatus(err) == http.StatusServiceUnavailable { 677 tlog.Logln("Waiting for the cluster to start up...") 678 time.Sleep(WaitClusterStartup) 679 continue 680 } 681 682 tlog.Logf("Unable to get usable cluster map: %v\n", err) 683 if len(ts) > 0 { 684 tassert.CheckFatal(ts[0], err) 685 } 686 return nil, err 687 } 688 return smap, nil 689 } 690 }