storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/storage-rest-client.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2018-2020 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/gob" 23 "encoding/hex" 24 "errors" 25 "io" 26 "io/ioutil" 27 "net/url" 28 "path" 29 "strconv" 30 "strings" 31 "sync" 32 "time" 33 34 xbufio "github.com/philhofer/fwd" 35 "github.com/tinylib/msgp/msgp" 36 37 "storj.io/minio/cmd/http" 38 xhttp "storj.io/minio/cmd/http" 39 "storj.io/minio/cmd/logger" 40 "storj.io/minio/cmd/rest" 41 xnet "storj.io/minio/pkg/net" 42 ) 43 44 func isNetworkError(err error) bool { 45 if err == nil { 46 return false 47 } 48 if nerr, ok := err.(*rest.NetworkError); ok { 49 return xnet.IsNetworkOrHostDown(nerr.Err, false) 50 } 51 return false 52 } 53 54 // Converts network error to storageErr. This function is 55 // written so that the storageAPI errors are consistent 56 // across network disks. 57 func toStorageErr(err error) error { 58 if err == nil { 59 return nil 60 } 61 62 if isNetworkError(err) { 63 return errDiskNotFound 64 } 65 66 switch err.Error() { 67 case errFaultyDisk.Error(): 68 return errFaultyDisk 69 case errFileCorrupt.Error(): 70 return errFileCorrupt 71 case errUnexpected.Error(): 72 return errUnexpected 73 case errDiskFull.Error(): 74 return errDiskFull 75 case errVolumeNotFound.Error(): 76 return errVolumeNotFound 77 case errVolumeExists.Error(): 78 return errVolumeExists 79 case errFileNotFound.Error(): 80 return errFileNotFound 81 case errFileVersionNotFound.Error(): 82 return errFileVersionNotFound 83 case errFileNameTooLong.Error(): 84 return errFileNameTooLong 85 case errFileAccessDenied.Error(): 86 return errFileAccessDenied 87 case errPathNotFound.Error(): 88 return errPathNotFound 89 case errIsNotRegular.Error(): 90 return errIsNotRegular 91 case errVolumeNotEmpty.Error(): 92 return errVolumeNotEmpty 93 case errVolumeAccessDenied.Error(): 94 return errVolumeAccessDenied 95 case errCorruptedFormat.Error(): 96 return errCorruptedFormat 97 case errUnformattedDisk.Error(): 98 return errUnformattedDisk 99 case errInvalidAccessKeyID.Error(): 100 return errInvalidAccessKeyID 101 case errAuthentication.Error(): 102 return errAuthentication 103 case errRPCAPIVersionUnsupported.Error(): 104 return errRPCAPIVersionUnsupported 105 case errServerTimeMismatch.Error(): 106 return errServerTimeMismatch 107 case io.EOF.Error(): 108 return io.EOF 109 case io.ErrUnexpectedEOF.Error(): 110 return io.ErrUnexpectedEOF 111 case errDiskStale.Error(): 112 return errDiskNotFound 113 case errDiskNotFound.Error(): 114 return errDiskNotFound 115 } 116 return err 117 } 118 119 // Abstracts a remote disk. 120 type storageRESTClient struct { 121 endpoint Endpoint 122 restClient *rest.Client 123 diskID string 124 125 // Indexes, will be -1 until assigned a set. 126 poolIndex, setIndex, diskIndex int 127 128 diskInfoCache timedValue 129 diskHealCache timedValue 130 } 131 132 // Retrieve location indexes. 133 func (client *storageRESTClient) GetDiskLoc() (poolIdx, setIdx, diskIdx int) { 134 return client.poolIndex, client.setIndex, client.diskIndex 135 } 136 137 // Set location indexes. 138 func (client *storageRESTClient) SetDiskLoc(poolIdx, setIdx, diskIdx int) { 139 client.poolIndex = poolIdx 140 client.setIndex = setIdx 141 client.diskIndex = diskIdx 142 } 143 144 // Wrapper to restClient.Call to handle network errors, in case of network error the connection is makred disconnected 145 // permanently. The only way to restore the storage connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints() 146 // after verifying format.json 147 func (client *storageRESTClient) call(ctx context.Context, method string, values url.Values, body io.Reader, length int64) (io.ReadCloser, error) { 148 if values == nil { 149 values = make(url.Values) 150 } 151 values.Set(storageRESTDiskID, client.diskID) 152 respBody, err := client.restClient.Call(ctx, method, values, body, length) 153 if err == nil { 154 return respBody, nil 155 } 156 157 err = toStorageErr(err) 158 return nil, err 159 } 160 161 // Stringer provides a canonicalized representation of network device. 162 func (client *storageRESTClient) String() string { 163 return client.endpoint.String() 164 } 165 166 // IsOnline - returns whether RPC client failed to connect or not. 167 func (client *storageRESTClient) IsOnline() bool { 168 return client.restClient.IsOnline() 169 } 170 171 func (client *storageRESTClient) IsLocal() bool { 172 return false 173 } 174 175 func (client *storageRESTClient) Hostname() string { 176 return client.endpoint.Host 177 } 178 179 func (client *storageRESTClient) Endpoint() Endpoint { 180 return client.endpoint 181 } 182 183 func (client *storageRESTClient) Healing() *healingTracker { 184 client.diskHealCache.Once.Do(func() { 185 // Update at least every second. 186 client.diskHealCache.TTL = time.Second 187 client.diskHealCache.Update = func() (interface{}, error) { 188 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 189 defer cancel() 190 b, err := client.ReadAll(ctx, minioMetaBucket, 191 pathJoin(bucketMetaPrefix, healingTrackerFilename)) 192 if err != nil { 193 // If error, likely not healing. 194 return (*healingTracker)(nil), nil 195 } 196 var h healingTracker 197 _, err = h.UnmarshalMsg(b) 198 return &h, err 199 } 200 }) 201 val, _ := client.diskHealCache.Get() 202 return val.(*healingTracker) 203 } 204 205 func (client *storageRESTClient) NSScanner(ctx context.Context, cache dataUsageCache) (dataUsageCache, error) { 206 pr, pw := io.Pipe() 207 go func() { 208 pw.CloseWithError(cache.serializeTo(pw)) 209 }() 210 respBody, err := client.call(ctx, storageRESTMethodNSScanner, url.Values{}, pr, -1) 211 defer http.DrainBody(respBody) 212 if err != nil { 213 pr.Close() 214 return cache, err 215 } 216 pr.Close() 217 218 var newCache dataUsageCache 219 pr, pw = io.Pipe() 220 go func() { 221 pw.CloseWithError(waitForHTTPStream(respBody, pw)) 222 }() 223 err = newCache.deserialize(pr) 224 pr.CloseWithError(err) 225 return newCache, err 226 } 227 228 func (client *storageRESTClient) GetDiskID() (string, error) { 229 // This call should never be over the network, this is always 230 // a cached value - caller should make sure to use this 231 // function on a fresh disk or make sure to look at the error 232 // from a different networked call to validate the GetDiskID() 233 return client.diskID, nil 234 } 235 236 func (client *storageRESTClient) SetDiskID(id string) { 237 client.diskID = id 238 } 239 240 // DiskInfo - fetch disk information for a remote disk. 241 func (client *storageRESTClient) DiskInfo(ctx context.Context) (info DiskInfo, err error) { 242 client.diskInfoCache.Once.Do(func() { 243 client.diskInfoCache.TTL = time.Second 244 client.diskInfoCache.Update = func() (interface{}, error) { 245 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 246 defer cancel() 247 respBody, err := client.call(ctx, storageRESTMethodDiskInfo, nil, nil, -1) 248 if err != nil { 249 return info, err 250 } 251 defer http.DrainBody(respBody) 252 if err = msgp.Decode(respBody, &info); err != nil { 253 return info, err 254 } 255 if info.Error != "" { 256 return info, toStorageErr(errors.New(info.Error)) 257 } 258 return info, nil 259 } 260 }) 261 val, err := client.diskInfoCache.Get() 262 if err == nil { 263 info = val.(DiskInfo) 264 } 265 266 return info, err 267 } 268 269 // MakeVolBulk - create multiple volumes in a bulk operation. 270 func (client *storageRESTClient) MakeVolBulk(ctx context.Context, volumes ...string) (err error) { 271 values := make(url.Values) 272 values.Set(storageRESTVolumes, strings.Join(volumes, ",")) 273 respBody, err := client.call(ctx, storageRESTMethodMakeVolBulk, values, nil, -1) 274 defer http.DrainBody(respBody) 275 return err 276 } 277 278 // MakeVol - create a volume on a remote disk. 279 func (client *storageRESTClient) MakeVol(ctx context.Context, volume string) (err error) { 280 values := make(url.Values) 281 values.Set(storageRESTVolume, volume) 282 respBody, err := client.call(ctx, storageRESTMethodMakeVol, values, nil, -1) 283 defer http.DrainBody(respBody) 284 return err 285 } 286 287 // ListVols - List all volumes on a remote disk. 288 func (client *storageRESTClient) ListVols(ctx context.Context) (vols []VolInfo, err error) { 289 respBody, err := client.call(ctx, storageRESTMethodListVols, nil, nil, -1) 290 if err != nil { 291 return 292 } 293 defer http.DrainBody(respBody) 294 vinfos := VolsInfo(vols) 295 err = msgp.Decode(respBody, &vinfos) 296 return vinfos, err 297 } 298 299 // StatVol - get volume info over the network. 300 func (client *storageRESTClient) StatVol(ctx context.Context, volume string) (vol VolInfo, err error) { 301 values := make(url.Values) 302 values.Set(storageRESTVolume, volume) 303 respBody, err := client.call(ctx, storageRESTMethodStatVol, values, nil, -1) 304 if err != nil { 305 return 306 } 307 defer http.DrainBody(respBody) 308 err = msgp.Decode(respBody, &vol) 309 return vol, err 310 } 311 312 // DeleteVol - Deletes a volume over the network. 313 func (client *storageRESTClient) DeleteVol(ctx context.Context, volume string, forceDelete bool) (err error) { 314 values := make(url.Values) 315 values.Set(storageRESTVolume, volume) 316 if forceDelete { 317 values.Set(storageRESTForceDelete, "true") 318 } 319 respBody, err := client.call(ctx, storageRESTMethodDeleteVol, values, nil, -1) 320 defer http.DrainBody(respBody) 321 return err 322 } 323 324 // AppendFile - append to a file. 325 func (client *storageRESTClient) AppendFile(ctx context.Context, volume string, path string, buf []byte) error { 326 values := make(url.Values) 327 values.Set(storageRESTVolume, volume) 328 values.Set(storageRESTFilePath, path) 329 reader := bytes.NewReader(buf) 330 respBody, err := client.call(ctx, storageRESTMethodAppendFile, values, reader, -1) 331 defer http.DrainBody(respBody) 332 return err 333 } 334 335 func (client *storageRESTClient) CreateFile(ctx context.Context, volume, path string, size int64, reader io.Reader) error { 336 values := make(url.Values) 337 values.Set(storageRESTVolume, volume) 338 values.Set(storageRESTFilePath, path) 339 values.Set(storageRESTLength, strconv.Itoa(int(size))) 340 respBody, err := client.call(ctx, storageRESTMethodCreateFile, values, ioutil.NopCloser(reader), size) 341 defer http.DrainBody(respBody) 342 return err 343 } 344 345 func (client *storageRESTClient) WriteMetadata(ctx context.Context, volume, path string, fi FileInfo) error { 346 values := make(url.Values) 347 values.Set(storageRESTVolume, volume) 348 values.Set(storageRESTFilePath, path) 349 350 var reader bytes.Buffer 351 if err := msgp.Encode(&reader, &fi); err != nil { 352 return err 353 } 354 355 respBody, err := client.call(ctx, storageRESTMethodWriteMetadata, values, &reader, -1) 356 defer http.DrainBody(respBody) 357 return err 358 } 359 360 func (client *storageRESTClient) UpdateMetadata(ctx context.Context, volume, path string, fi FileInfo) error { 361 values := make(url.Values) 362 values.Set(storageRESTVolume, volume) 363 values.Set(storageRESTFilePath, path) 364 365 var reader bytes.Buffer 366 if err := msgp.Encode(&reader, &fi); err != nil { 367 return err 368 } 369 370 respBody, err := client.call(ctx, storageRESTMethodUpdateMetadata, values, &reader, -1) 371 defer http.DrainBody(respBody) 372 return err 373 } 374 375 func (client *storageRESTClient) DeleteVersion(ctx context.Context, volume, path string, fi FileInfo, forceDelMarker bool) error { 376 values := make(url.Values) 377 values.Set(storageRESTVolume, volume) 378 values.Set(storageRESTFilePath, path) 379 values.Set(storageRESTForceDelMarker, strconv.FormatBool(forceDelMarker)) 380 381 var buffer bytes.Buffer 382 if err := msgp.Encode(&buffer, &fi); err != nil { 383 return err 384 } 385 386 respBody, err := client.call(ctx, storageRESTMethodDeleteVersion, values, &buffer, -1) 387 defer http.DrainBody(respBody) 388 return err 389 } 390 391 // WriteAll - write all data to a file. 392 func (client *storageRESTClient) WriteAll(ctx context.Context, volume string, path string, b []byte) error { 393 values := make(url.Values) 394 values.Set(storageRESTVolume, volume) 395 values.Set(storageRESTFilePath, path) 396 respBody, err := client.call(ctx, storageRESTMethodWriteAll, values, bytes.NewBuffer(b), int64(len(b))) 397 defer http.DrainBody(respBody) 398 return err 399 } 400 401 // CheckFile - stat a file metadata. 402 func (client *storageRESTClient) CheckFile(ctx context.Context, volume string, path string) error { 403 values := make(url.Values) 404 values.Set(storageRESTVolume, volume) 405 values.Set(storageRESTFilePath, path) 406 respBody, err := client.call(ctx, storageRESTMethodCheckFile, values, nil, -1) 407 defer http.DrainBody(respBody) 408 return err 409 } 410 411 // CheckParts - stat all file parts. 412 func (client *storageRESTClient) CheckParts(ctx context.Context, volume string, path string, fi FileInfo) error { 413 values := make(url.Values) 414 values.Set(storageRESTVolume, volume) 415 values.Set(storageRESTFilePath, path) 416 417 var reader bytes.Buffer 418 if err := msgp.Encode(&reader, &fi); err != nil { 419 logger.LogIf(context.Background(), err) 420 return err 421 } 422 423 respBody, err := client.call(ctx, storageRESTMethodCheckParts, values, &reader, -1) 424 defer http.DrainBody(respBody) 425 return err 426 } 427 428 // RenameData - rename source path to destination path atomically, metadata and data file. 429 func (client *storageRESTClient) RenameData(ctx context.Context, srcVolume, srcPath string, fi FileInfo, dstVolume, dstPath string) (err error) { 430 values := make(url.Values) 431 values.Set(storageRESTSrcVolume, srcVolume) 432 values.Set(storageRESTSrcPath, srcPath) 433 values.Set(storageRESTDstVolume, dstVolume) 434 values.Set(storageRESTDstPath, dstPath) 435 436 var reader bytes.Buffer 437 if err = msgp.Encode(&reader, &fi); err != nil { 438 return err 439 } 440 441 respBody, err := client.call(ctx, storageRESTMethodRenameData, values, &reader, -1) 442 defer http.DrainBody(respBody) 443 444 return err 445 } 446 447 // where we keep old *Readers 448 var readMsgpReaderPool = sync.Pool{New: func() interface{} { return &msgp.Reader{} }} 449 450 // mspNewReader returns a *Reader that reads from the provided reader. 451 // The reader will be buffered. 452 func msgpNewReader(r io.Reader) *msgp.Reader { 453 p := readMsgpReaderPool.Get().(*msgp.Reader) 454 if p.R == nil { 455 p.R = xbufio.NewReaderSize(r, 8<<10) 456 } else { 457 p.R.Reset(r) 458 } 459 return p 460 } 461 462 func (client *storageRESTClient) ReadVersion(ctx context.Context, volume, path, versionID string, readData bool) (fi FileInfo, err error) { 463 values := make(url.Values) 464 values.Set(storageRESTVolume, volume) 465 values.Set(storageRESTFilePath, path) 466 values.Set(storageRESTVersionID, versionID) 467 values.Set(storageRESTReadData, strconv.FormatBool(readData)) 468 469 respBody, err := client.call(ctx, storageRESTMethodReadVersion, values, nil, -1) 470 if err != nil { 471 return fi, err 472 } 473 defer http.DrainBody(respBody) 474 475 dec := msgpNewReader(respBody) 476 defer readMsgpReaderPool.Put(dec) 477 478 err = fi.DecodeMsg(dec) 479 return fi, err 480 } 481 482 // ReadAll - reads all contents of a file. 483 func (client *storageRESTClient) ReadAll(ctx context.Context, volume string, path string) ([]byte, error) { 484 values := make(url.Values) 485 values.Set(storageRESTVolume, volume) 486 values.Set(storageRESTFilePath, path) 487 respBody, err := client.call(ctx, storageRESTMethodReadAll, values, nil, -1) 488 if err != nil { 489 return nil, err 490 } 491 defer http.DrainBody(respBody) 492 return ioutil.ReadAll(respBody) 493 } 494 495 // ReadFileStream - returns a reader for the requested file. 496 func (client *storageRESTClient) ReadFileStream(ctx context.Context, volume, path string, offset, length int64) (io.ReadCloser, error) { 497 values := make(url.Values) 498 values.Set(storageRESTVolume, volume) 499 values.Set(storageRESTFilePath, path) 500 values.Set(storageRESTOffset, strconv.Itoa(int(offset))) 501 values.Set(storageRESTLength, strconv.Itoa(int(length))) 502 respBody, err := client.call(ctx, storageRESTMethodReadFileStream, values, nil, -1) 503 if err != nil { 504 return nil, err 505 } 506 return respBody, nil 507 } 508 509 // ReadFile - reads section of a file. 510 func (client *storageRESTClient) ReadFile(ctx context.Context, volume string, path string, offset int64, buf []byte, verifier *BitrotVerifier) (int64, error) { 511 values := make(url.Values) 512 values.Set(storageRESTVolume, volume) 513 values.Set(storageRESTFilePath, path) 514 values.Set(storageRESTOffset, strconv.Itoa(int(offset))) 515 values.Set(storageRESTLength, strconv.Itoa(len(buf))) 516 if verifier != nil { 517 values.Set(storageRESTBitrotAlgo, verifier.algorithm.String()) 518 values.Set(storageRESTBitrotHash, hex.EncodeToString(verifier.sum)) 519 } else { 520 values.Set(storageRESTBitrotAlgo, "") 521 values.Set(storageRESTBitrotHash, "") 522 } 523 respBody, err := client.call(ctx, storageRESTMethodReadFile, values, nil, -1) 524 if err != nil { 525 return 0, err 526 } 527 defer http.DrainBody(respBody) 528 n, err := io.ReadFull(respBody, buf) 529 return int64(n), err 530 } 531 532 // ListDir - lists a directory. 533 func (client *storageRESTClient) ListDir(ctx context.Context, volume, dirPath string, count int) (entries []string, err error) { 534 values := make(url.Values) 535 values.Set(storageRESTVolume, volume) 536 values.Set(storageRESTDirPath, dirPath) 537 values.Set(storageRESTCount, strconv.Itoa(count)) 538 respBody, err := client.call(ctx, storageRESTMethodListDir, values, nil, -1) 539 if err != nil { 540 return nil, err 541 } 542 defer http.DrainBody(respBody) 543 err = gob.NewDecoder(respBody).Decode(&entries) 544 return entries, err 545 } 546 547 // DeleteFile - deletes a file. 548 func (client *storageRESTClient) Delete(ctx context.Context, volume string, path string, recursive bool) error { 549 values := make(url.Values) 550 values.Set(storageRESTVolume, volume) 551 values.Set(storageRESTFilePath, path) 552 values.Set(storageRESTRecursive, strconv.FormatBool(recursive)) 553 554 respBody, err := client.call(ctx, storageRESTMethodDeleteFile, values, nil, -1) 555 defer http.DrainBody(respBody) 556 return err 557 } 558 559 // DeleteVersions - deletes list of specified versions if present 560 func (client *storageRESTClient) DeleteVersions(ctx context.Context, volume string, versions []FileInfo) (errs []error) { 561 if len(versions) == 0 { 562 return errs 563 } 564 565 values := make(url.Values) 566 values.Set(storageRESTVolume, volume) 567 values.Set(storageRESTTotalVersions, strconv.Itoa(len(versions))) 568 569 var buffer bytes.Buffer 570 encoder := msgp.NewWriter(&buffer) 571 for _, version := range versions { 572 version.EncodeMsg(encoder) 573 } 574 logger.LogIf(ctx, encoder.Flush()) 575 576 errs = make([]error, len(versions)) 577 578 respBody, err := client.call(ctx, storageRESTMethodDeleteVersions, values, &buffer, -1) 579 defer http.DrainBody(respBody) 580 if err != nil { 581 for i := range errs { 582 errs[i] = err 583 } 584 return errs 585 } 586 587 reader, err := waitForHTTPResponse(respBody) 588 if err != nil { 589 for i := range errs { 590 errs[i] = err 591 } 592 return errs 593 } 594 595 dErrResp := &DeleteVersionsErrsResp{} 596 if err = gob.NewDecoder(reader).Decode(dErrResp); err != nil { 597 for i := range errs { 598 errs[i] = err 599 } 600 return errs 601 } 602 603 for i, dErr := range dErrResp.Errs { 604 errs[i] = toStorageErr(dErr) 605 } 606 607 return errs 608 } 609 610 // RenameFile - renames a file. 611 func (client *storageRESTClient) RenameFile(ctx context.Context, srcVolume, srcPath, dstVolume, dstPath string) (err error) { 612 values := make(url.Values) 613 values.Set(storageRESTSrcVolume, srcVolume) 614 values.Set(storageRESTSrcPath, srcPath) 615 values.Set(storageRESTDstVolume, dstVolume) 616 values.Set(storageRESTDstPath, dstPath) 617 respBody, err := client.call(ctx, storageRESTMethodRenameFile, values, nil, -1) 618 defer http.DrainBody(respBody) 619 return err 620 } 621 622 func (client *storageRESTClient) VerifyFile(ctx context.Context, volume, path string, fi FileInfo) error { 623 values := make(url.Values) 624 values.Set(storageRESTVolume, volume) 625 values.Set(storageRESTFilePath, path) 626 627 var reader bytes.Buffer 628 if err := msgp.Encode(&reader, &fi); err != nil { 629 return err 630 } 631 632 respBody, err := client.call(ctx, storageRESTMethodVerifyFile, values, &reader, -1) 633 defer http.DrainBody(respBody) 634 if err != nil { 635 return err 636 } 637 638 respReader, err := waitForHTTPResponse(respBody) 639 if err != nil { 640 return err 641 } 642 643 verifyResp := &VerifyFileResp{} 644 if err = gob.NewDecoder(respReader).Decode(verifyResp); err != nil { 645 return err 646 } 647 648 return toStorageErr(verifyResp.Err) 649 } 650 651 // Close - marks the client as closed. 652 func (client *storageRESTClient) Close() error { 653 client.restClient.Close() 654 return nil 655 } 656 657 // Returns a storage rest client. 658 func newStorageRESTClient(endpoint Endpoint, healthcheck bool) *storageRESTClient { 659 serverURL := &url.URL{ 660 Scheme: endpoint.Scheme, 661 Host: endpoint.Host, 662 Path: path.Join(storageRESTPrefix, endpoint.Path, storageRESTVersion), 663 } 664 665 restClient := rest.NewClient(serverURL, globalInternodeTransport, newAuthToken) 666 667 if healthcheck { 668 // Use a separate client to avoid recursive calls. 669 healthClient := rest.NewClient(serverURL, globalInternodeTransport, newAuthToken) 670 healthClient.ExpectTimeouts = true 671 restClient.HealthCheckFn = func() bool { 672 ctx, cancel := context.WithTimeout(context.Background(), restClient.HealthCheckTimeout) 673 defer cancel() 674 respBody, err := healthClient.Call(ctx, storageRESTMethodHealth, nil, nil, -1) 675 xhttp.DrainBody(respBody) 676 return toStorageErr(err) != errDiskNotFound 677 } 678 } 679 680 return &storageRESTClient{endpoint: endpoint, restClient: restClient, poolIndex: -1, setIndex: -1, diskIndex: -1} 681 }