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 }