github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/api/bsumm.go (about)

     1  // Package api provides native Go-based API/SDK over HTTP(S).
     2  /*
     3   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package api
     6  
     7  import (
     8  	"fmt"
     9  	"net/http"
    10  	"net/url"
    11  	"sort"
    12  	"strconv"
    13  	"time"
    14  
    15  	"github.com/NVIDIA/aistore/api/apc"
    16  	"github.com/NVIDIA/aistore/cmn"
    17  	"github.com/NVIDIA/aistore/cmn/cos"
    18  	"github.com/NVIDIA/aistore/cmn/debug"
    19  	"github.com/NVIDIA/aistore/cmn/mono"
    20  	"github.com/NVIDIA/aistore/xact"
    21  	jsoniter "github.com/json-iterator/go"
    22  )
    23  
    24  const fmtErrStatus = "expecting status (ok, partial, accepted), or error - got %d"
    25  
    26  type (
    27  	BsummCB func(*cmn.AllBsummResults, bool)
    28  
    29  	BsummArgs struct {
    30  		Callback  BsummCB
    31  		CallAfter time.Duration
    32  		DontWait  bool
    33  	}
    34  	BinfoArgs struct {
    35  		BsummArgs
    36  		UUID          string
    37  		FltPresence   int
    38  		Summarize     bool
    39  		WithRemote    bool
    40  		DontAddRemote bool
    41  	}
    42  )
    43  
    44  // Bucket information - a runtime addendum to `BucketProps`.
    45  // In addition to `cmn.Bprops` properties (which are user configurable), bucket runtime info:
    46  // - includes usage, capacity, other statistics
    47  // - is obtained via GetBucketInfo() API
    48  // - and delivered via apc.HdrBucketInfo header (compare with GetBucketSummary)
    49  // The API uses http.MethodHead and can be considered an extension of HeadBucket (above)
    50  func GetBucketInfo(bp BaseParams, bck cmn.Bck, args BinfoArgs) (string, *cmn.Bprops, *cmn.BsummResult, error) {
    51  	q := make(url.Values, 4)
    52  	q = bck.AddToQuery(q)
    53  	q.Set(apc.QparamFltPresence, strconv.Itoa(args.FltPresence))
    54  	if args.DontAddRemote {
    55  		q.Set(apc.QparamDontAddRemote, "true")
    56  	}
    57  	if args.Summarize {
    58  		if args.WithRemote {
    59  			q.Set(apc.QparamBinfoWithOrWithoutRemote, "true")
    60  		} else {
    61  			q.Set(apc.QparamBinfoWithOrWithoutRemote, "false")
    62  		}
    63  	}
    64  	bp.Method = http.MethodHead
    65  	reqParams := AllocRp()
    66  	{
    67  		reqParams.BaseParams = bp
    68  		reqParams.Path = apc.URLPathBuckets.Join(bck.Name)
    69  		reqParams.Query = q
    70  	}
    71  	xid, p, info, err := _binfo(reqParams, bck, args)
    72  	FreeRp(reqParams)
    73  	return xid, p, info, err
    74  }
    75  
    76  // compare w/ _bsumm
    77  // TODO: _binfoDontWait w/ ref
    78  func _binfo(reqParams *ReqParams, bck cmn.Bck, args BinfoArgs) (xid string, p *cmn.Bprops, info *cmn.BsummResult, err error) {
    79  	var (
    80  		hdr          http.Header
    81  		status       int
    82  		start, after int64
    83  		sleep        = xact.MinPollTime
    84  		news         = args.UUID == ""
    85  	)
    86  	if !news {
    87  		reqParams.Query.Set(apc.QparamUUID, xid)
    88  	}
    89  	if hdr, status, err = reqParams.doReqHdr(); err != nil {
    90  		err = hdr2msg(bck, status, err)
    91  		return
    92  	}
    93  
    94  	hdrProps := hdr.Get(apc.HdrBucketProps)
    95  	if hdrProps != "" {
    96  		p = &cmn.Bprops{}
    97  		if err = jsoniter.Unmarshal([]byte(hdrProps), p); err != nil {
    98  			return
    99  		}
   100  	}
   101  	xid = hdr.Get(apc.HdrXactionID)
   102  	if xid == "" {
   103  		debug.Assert(status == http.StatusOK && !args.Summarize, status, " ", args.Summarize)
   104  		return
   105  	}
   106  	debug.Assert(news || xid == args.UUID)
   107  	if args.DontWait {
   108  		if (status == http.StatusOK || status == http.StatusPartialContent) && args.Summarize {
   109  			if hdrSumm := hdr.Get(apc.HdrBucketSumm); hdrSumm != "" {
   110  				info = &cmn.BsummResult{}
   111  				err = jsoniter.Unmarshal([]byte(hdrSumm), info)
   112  			}
   113  		}
   114  		return
   115  	}
   116  	if status != http.StatusAccepted {
   117  		err = _invalidStatus(status)
   118  		return
   119  	}
   120  	if args.Callback != nil {
   121  		start = mono.NanoTime()
   122  		after = start + args.CallAfter.Nanoseconds()
   123  	}
   124  
   125  	if news {
   126  		reqParams.Query.Set(apc.QparamUUID, xid)
   127  	}
   128  	time.Sleep(sleep / 2)
   129  	for i := 0; ; i++ {
   130  		if hdr, status, err = reqParams.doReqHdr(); err != nil {
   131  			err = hdr2msg(bck, status, err)
   132  			return
   133  		}
   134  
   135  		hdrSumm := hdr.Get(apc.HdrBucketSumm)
   136  		if hdrSumm != "" {
   137  			info = &cmn.BsummResult{}
   138  			err = jsoniter.Unmarshal([]byte(hdrSumm), info)
   139  		}
   140  		if err != nil {
   141  			return // unlikely
   142  		}
   143  		debug.Assertf(hdr.Get(apc.HdrXactionID) == xid, "%q vs %q", hdr.Get(apc.HdrXactionID), xid)
   144  
   145  		// callback w/ partial results
   146  		if args.Callback != nil && (status == http.StatusPartialContent || status == http.StatusOK) {
   147  			if after == start || mono.NanoTime() >= after {
   148  				res := cmn.AllBsummResults{info}
   149  				args.Callback(&res, status == http.StatusOK)
   150  			}
   151  		}
   152  		if status == http.StatusOK {
   153  			return
   154  		}
   155  		if status != http.StatusPartialContent && status != http.StatusAccepted {
   156  			err = _invalidStatus(status)
   157  			return
   158  		}
   159  
   160  		time.Sleep(sleep)
   161  		// inc. sleep time if there's nothing at all
   162  		if i == 8 && status != http.StatusPartialContent {
   163  			sleep *= 2
   164  		} else if i == 16 && status != http.StatusPartialContent {
   165  			sleep *= 2
   166  		}
   167  	}
   168  }
   169  
   170  // GetBucketSummary returns bucket capacity ulitization percentages, sizes (total and min/max/average),
   171  // and the numbers of objects, both _in_ the cluster and remote
   172  // GetBucketSummary supports a single specified bucket or multiple buckets, as per `cmn.QueryBcks` query.
   173  // (e.g., GetBucketSummary with an empty bucket query will return "summary" info for all buckets)
   174  func GetBucketSummary(bp BaseParams, qbck cmn.QueryBcks, msg *apc.BsummCtrlMsg, args BsummArgs) (xid string,
   175  	res cmn.AllBsummResults, err error) {
   176  	if msg == nil {
   177  		msg = &apc.BsummCtrlMsg{ObjCached: true, BckPresent: true}
   178  	}
   179  	bp.Method = http.MethodGet
   180  
   181  	reqParams := AllocRp()
   182  	{
   183  		reqParams.BaseParams = bp
   184  		reqParams.Path = apc.URLPathBuckets.Join(qbck.Name)
   185  		reqParams.Header = http.Header{cos.HdrContentType: []string{cos.ContentJSON}}
   186  		reqParams.Query = qbck.NewQuery()
   187  	}
   188  	if args.DontWait {
   189  		debug.Assert(args.Callback == nil)
   190  		xid, err = _bsummDontWait(reqParams, msg, &res)
   191  	} else {
   192  		xid, err = _bsumm(reqParams, msg, &res, args)
   193  	}
   194  	if err == nil {
   195  		sort.Sort(res)
   196  	}
   197  	FreeRp(reqParams)
   198  	return
   199  }
   200  
   201  // Wait/poll bucket-summary:
   202  // - initiate `apc.ActSummaryBck` (msg.UUID == "").
   203  // - poll for status != ok
   204  // - handle status: ok, accepted, partial-content
   205  // See also:
   206  // - _binfo
   207  // - _bsummDontWait
   208  func _bsumm(reqParams *ReqParams, msg *apc.BsummCtrlMsg, res *cmn.AllBsummResults, args BsummArgs) (xid string, _ error) {
   209  	var (
   210  		start, after int64
   211  		sleep        = xact.MinPollTime
   212  		actMsg       = apc.ActMsg{Action: apc.ActSummaryBck, Value: msg}
   213  	)
   214  	debug.Assert(msg.UUID == "")
   215  	if args.Callback != nil {
   216  		start = mono.NanoTime()
   217  		after = start + args.CallAfter.Nanoseconds()
   218  	}
   219  	reqParams.Body = cos.MustMarshal(actMsg)
   220  	status, err := reqParams.doReqStr(&xid)
   221  	if err != nil {
   222  		return xid, err
   223  	}
   224  	if status != http.StatusAccepted {
   225  		return xid, _invalidStatus(status)
   226  	}
   227  	if msg.UUID == "" {
   228  		msg.UUID = xid
   229  		reqParams.Body = cos.MustMarshal(actMsg)
   230  	}
   231  
   232  	time.Sleep(sleep / 2)
   233  	for i := 0; ; i++ {
   234  		status, err = reqParams.DoReqAny(res)
   235  		if err != nil {
   236  			return xid, err
   237  		}
   238  		// callback w/ partial results
   239  		if args.Callback != nil && (status == http.StatusPartialContent || status == http.StatusOK) {
   240  			if after == start || mono.NanoTime() >= after {
   241  				args.Callback(res, status == http.StatusOK)
   242  			}
   243  		}
   244  		if status == http.StatusOK {
   245  			return xid, nil
   246  		}
   247  		if status != http.StatusPartialContent && status != http.StatusAccepted {
   248  			return xid, _invalidStatus(status)
   249  		}
   250  
   251  		time.Sleep(sleep)
   252  		// inc. sleep time if there's nothing at all
   253  		if i == 8 && status != http.StatusPartialContent {
   254  			sleep *= 2
   255  		} else if i == 16 && status != http.StatusPartialContent {
   256  			sleep *= 2
   257  		}
   258  	}
   259  }
   260  
   261  func _bsummDontWait(reqParams *ReqParams, msg *apc.BsummCtrlMsg, res *cmn.AllBsummResults) (xid string, err error) {
   262  	var (
   263  		actMsg = apc.ActMsg{Action: apc.ActSummaryBck, Value: msg}
   264  		status int
   265  		news   = msg.UUID == ""
   266  	)
   267  	reqParams.Body = cos.MustMarshal(actMsg)
   268  	if news {
   269  		status, err = reqParams.doReqStr(&xid)
   270  		if err == nil {
   271  			if status != http.StatusAccepted {
   272  				err = _invalidStatus(status)
   273  			}
   274  		}
   275  		return
   276  	}
   277  
   278  	status, err = reqParams.DoReqAny(res)
   279  	if err != nil {
   280  		return xid, err
   281  	}
   282  	switch status {
   283  	case http.StatusOK:
   284  	case http.StatusPartialContent, http.StatusAccepted:
   285  		err = &cmn.ErrHTTP{Message: http.StatusText(status), Status: status} // indicate
   286  	default:
   287  		err = _invalidStatus(status)
   288  	}
   289  	return
   290  }
   291  
   292  func _invalidStatus(status int) error {
   293  	return &cmn.ErrHTTP{
   294  		Message: fmt.Sprintf(fmtErrStatus, status),
   295  		Status:  status,
   296  	}
   297  }