github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ais/tgtbck.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 "context" 9 "net/http" 10 "runtime" 11 "sort" 12 "strconv" 13 14 "github.com/NVIDIA/aistore/api/apc" 15 "github.com/NVIDIA/aistore/cmn" 16 "github.com/NVIDIA/aistore/cmn/cos" 17 "github.com/NVIDIA/aistore/cmn/debug" 18 "github.com/NVIDIA/aistore/cmn/mono" 19 "github.com/NVIDIA/aistore/cmn/nlog" 20 "github.com/NVIDIA/aistore/core" 21 "github.com/NVIDIA/aistore/core/meta" 22 "github.com/NVIDIA/aistore/fs" 23 "github.com/NVIDIA/aistore/nl" 24 "github.com/NVIDIA/aistore/reb" 25 "github.com/NVIDIA/aistore/stats" 26 "github.com/NVIDIA/aistore/xact" 27 "github.com/NVIDIA/aistore/xact/xreg" 28 "github.com/NVIDIA/aistore/xact/xs" 29 ) 30 31 // 32 // httpbck* handlers: list buckets/objects, summary (ditto), delete(bucket), head(bucket) 33 // 34 35 // GET /v1/buckets[/bucket-name] 36 func (t *target) httpbckget(w http.ResponseWriter, r *http.Request, dpq *dpq) { 37 apiItems, err := t.parseURL(w, r, apc.URLPathBuckets.L, 0, true) 38 if err != nil { 39 return 40 } 41 if err = t.isIntraCall(r.Header, false); err != nil { 42 t.writeErr(w, r, err) 43 return 44 } 45 msg, err := t.readAisMsg(w, r) 46 if err != nil { 47 return 48 } 49 t.ensureLatestBMD(msg, r) 50 51 if err := dpq.parse(r.URL.RawQuery); err != nil { 52 t.writeErr(w, r, err) 53 return 54 } 55 56 switch msg.Action { 57 case apc.ActList: 58 var bckName string 59 if len(apiItems) > 0 { 60 bckName = apiItems[0] 61 } 62 qbck, err := newQbckFromQ(bckName, nil, dpq) 63 if err != nil { 64 t.writeErr(w, r, err) 65 return 66 } 67 // list buckets if `qbck` indicates a bucket-type query 68 // (see api.ListBuckets and the line below) 69 if !qbck.IsBucket() { 70 qbck.Name = msg.Name 71 t.listBuckets(w, r, qbck) 72 return 73 } 74 bck := meta.CloneBck((*cmn.Bck)(qbck)) 75 if err := bck.Init(t.owner.bmd); err != nil { 76 if cmn.IsErrRemoteBckNotFound(err) { 77 if dpq.dontAddRemote { 78 // don't add it - proceed anyway (TODO: assert(wantOnlyRemote) below) 79 err = nil 80 } else { 81 // in an inlikely case updated BMD's in flight 82 t.BMDVersionFixup(r) 83 err = bck.Init(t.owner.bmd) 84 } 85 } 86 if err != nil { 87 t.statsT.IncErr(stats.ListCount) 88 t.writeErr(w, r, err) 89 return 90 } 91 } 92 var ( 93 begin = mono.NanoTime() 94 lsmsg *apc.LsoMsg 95 ) 96 if err := cos.MorphMarshal(msg.Value, &lsmsg); err != nil { 97 t.writeErrf(w, r, cmn.FmtErrMorphUnmarshal, t.si, msg.Action, msg.Value, err) 98 return 99 } 100 if !cos.IsValidUUID(lsmsg.UUID) { 101 debug.Assert(false, lsmsg.UUID) 102 t.writeErrf(w, r, "list-objects: invalid UUID %q", lsmsg.UUID) 103 return 104 } 105 if ok := t.listObjects(w, r, bck, lsmsg); !ok { 106 t.statsT.IncErr(stats.ListCount) 107 return 108 } 109 delta := mono.SinceNano(begin) 110 t.statsT.AddMany( 111 cos.NamedVal64{Name: stats.ListCount, Value: 1}, 112 cos.NamedVal64{Name: stats.ListLatency, Value: delta}, 113 ) 114 case apc.ActSummaryBck: 115 var bucket, phase string // txn 116 if len(apiItems) == 0 { 117 t.writeErrURL(w, r) 118 return 119 } 120 if len(apiItems) == 1 { 121 phase = apiItems[0] 122 } else { 123 bucket, phase = apiItems[0], apiItems[1] 124 } 125 if phase != apc.ActBegin && phase != apc.ActQuery { 126 t.writeErrURL(w, r) 127 return 128 } 129 130 qbck, err := newQbckFromQ(bucket, nil, dpq) 131 if err != nil { 132 t.writeErr(w, r, err) 133 return 134 } 135 136 var bsumMsg apc.BsummCtrlMsg 137 if err := cos.MorphMarshal(msg.Value, &bsumMsg); err != nil { 138 t.writeErrf(w, r, cmn.FmtErrMorphUnmarshal, t.si, msg.Action, msg.Value, err) 139 return 140 } 141 // if in fact it is a specific named bucket 142 bck := (*meta.Bck)(qbck) 143 if qbck.IsBucket() { 144 if err := bck.Init(t.owner.bmd); err != nil { 145 if cmn.IsErrRemoteBckNotFound(err) { 146 if bsumMsg.DontAddRemote || dpq.dontAddRemote { 147 // don't add it - proceed anyway 148 err = nil 149 } else { 150 t.BMDVersionFixup(r) 151 err = bck.Init(t.owner.bmd) 152 } 153 } 154 if err != nil { 155 t.writeErr(w, r, err) 156 return 157 } 158 } 159 } 160 t.bsumm(w, r, phase, bck, &bsumMsg, dpq) 161 default: 162 t.writeErrAct(w, r, msg.Action) 163 } 164 } 165 166 // there's a difference between looking for all (any) provider vs a specific one - 167 // in the former case the fact that (the corresponding backend is not configured) 168 // is not an error 169 func (t *target) listBuckets(w http.ResponseWriter, r *http.Request, qbck *cmn.QueryBcks) { 170 var ( 171 bcks cmn.Bcks 172 config = cmn.GCO.Get() 173 bmd = t.owner.bmd.get() 174 err error 175 code int 176 ) 177 if qbck.Provider != "" { 178 if qbck.IsAIS() || qbck.IsHTTP() { // built-in providers 179 bcks = bmd.Select(qbck) 180 } else { 181 bcks, code, err = t.blist(qbck, config) 182 if err != nil { 183 if _, ok := err.(*cmn.ErrMissingBackend); !ok { 184 err = cmn.NewErrFailedTo(t, "list buckets", qbck.String(), err, code) 185 } 186 t.writeErr(w, r, err, code) 187 return 188 } 189 } 190 } else /* all providers */ { 191 for provider := range apc.Providers { 192 var buckets cmn.Bcks 193 qbck.Provider = provider 194 if qbck.IsAIS() || qbck.IsHTTP() { 195 buckets = bmd.Select(qbck) 196 } else { 197 buckets, code, err = t.blist(qbck, config) 198 if err != nil { 199 if _, ok := err.(*cmn.ErrMissingBackend); !ok { // note on top of this func 200 t.writeErr(w, r, err, code) 201 return 202 } 203 } 204 } 205 bcks = append(bcks, buckets...) 206 } 207 } 208 209 sort.Sort(bcks) 210 t.writeJSON(w, r, bcks, "list-buckets") 211 } 212 213 func (t *target) blist(qbck *cmn.QueryBcks, config *cmn.Config) (bcks cmn.Bcks, ecode int, err error) { 214 // validate 215 debug.Assert(!qbck.IsAIS()) 216 if qbck.IsCloud() { // must be configured 217 if config.Backend.Get(qbck.Provider) == nil { 218 err = &cmn.ErrMissingBackend{Provider: qbck.Provider} 219 return 220 } 221 } else if qbck.IsRemoteAIS() && qbck.Ns.IsAnyRemote() { 222 if config.Backend.Get(apc.AIS) == nil { 223 nlog.Warningln(&cmn.ErrMissingBackend{Provider: qbck.Provider, Msg: "no remote ais clusters"}) 224 return 225 // otherwise go ahead and try to list below 226 } 227 } 228 backend := t.Backend((*meta.Bck)(qbck)) 229 if qbck.IsBucket() { 230 var ( 231 bck = (*meta.Bck)(qbck) 232 ctx = context.Background() 233 ) 234 _, ecode, err = backend.HeadBucket(ctx, bck) 235 if err == nil { 236 bcks = cmn.Bcks{bck.Clone()} 237 } else if ecode == http.StatusNotFound { 238 err = nil 239 } 240 } else { 241 bcks, ecode, err = backend.ListBuckets(*qbck) 242 } 243 if err == nil && len(bcks) > 1 { 244 sort.Sort(bcks) 245 } 246 return 247 } 248 249 // returns `cmn.LsoRes` containing object names and (requested) props 250 // control/scope - via `apc.LsoMsg` 251 func (t *target) listObjects(w http.ResponseWriter, r *http.Request, bck *meta.Bck, lsmsg *apc.LsoMsg) (ok bool) { 252 // (advanced) user-selected target to execute remote ls 253 if lsmsg.SID != "" { 254 smap := t.owner.smap.get() 255 if smap.GetTarget(lsmsg.SID) == nil { 256 err := &errNodeNotFound{"list-objects failure:", lsmsg.SID, t.si, smap} 257 t.writeErr(w, r, err) 258 return 259 } 260 } 261 262 var ( 263 xctn core.Xact 264 rns = xreg.RenewLso(bck, lsmsg.UUID, lsmsg, r.Header) 265 ) 266 // check that xaction hasn't finished prior to this page read, restart if needed 267 if rns.Err == xs.ErrGone { 268 runtime.Gosched() 269 rns = xreg.RenewLso(bck, lsmsg.UUID, lsmsg, r.Header) 270 } 271 if rns.Err != nil { 272 t.writeErr(w, r, rns.Err) 273 return 274 } 275 // run 276 xctn = rns.Entry.Get() 277 if !rns.IsRunning() { 278 xact.GoRunW(xctn) 279 } 280 xls := xctn.(*xs.LsoXact) 281 282 // NOTE: blocking next-page request 283 resp := xls.Do(lsmsg) 284 if resp.Err != nil { 285 t.writeErr(w, r, resp.Err, resp.Status) 286 return false 287 } 288 debug.Assert(resp.Lst.UUID == lsmsg.UUID) 289 290 // TODO: `Flags` have limited usability, consider to remove 291 marked := xreg.GetRebMarked() 292 if marked.Xact != nil || marked.Interrupted || reb.IsGFN() { 293 resp.Lst.Flags = 1 294 } 295 296 return t.writeMsgPack(w, resp.Lst, "list_objects") 297 } 298 299 func (t *target) bsumm(w http.ResponseWriter, r *http.Request, phase string, bck *meta.Bck, msg *apc.BsummCtrlMsg, dpq *dpq) { 300 if phase == apc.ActBegin { 301 rns := xreg.RenewBckSummary(bck, msg) 302 if rns.Err != nil { 303 t.writeErr(w, r, rns.Err, http.StatusInternalServerError) 304 return 305 } 306 w.WriteHeader(http.StatusAccepted) 307 return 308 } 309 310 debug.Assert(phase == apc.ActQuery, phase) 311 xctn, err := xreg.GetXact(msg.UUID) // vs. hk.OldAgeX removal 312 if err != nil { 313 t.writeErr(w, r, err, http.StatusInternalServerError) 314 return 315 } 316 317 // never started 318 if xctn == nil { 319 err := cos.NewErrNotFound(t, apc.ActSummaryBck+" job "+msg.UUID) 320 t._erris(w, r, dpq.silent, err, http.StatusNotFound) 321 return 322 } 323 324 xsumm := xctn.(*xs.XactNsumm) 325 result, err := xsumm.Result() 326 if err != nil { 327 if cmn.IsErrBucketNought(err) { 328 t.writeErr(w, r, err, http.StatusGone) 329 } else { 330 t.writeErr(w, r, err) 331 } 332 return 333 } 334 if !xctn.Finished() { 335 if len(result) == 0 { 336 w.WriteHeader(http.StatusAccepted) 337 } else { 338 w.WriteHeader(http.StatusPartialContent) 339 } 340 } 341 t.writeJSON(w, r, result, xsumm.Name()) 342 } 343 344 // DELETE { action } /v1/buckets/bucket-name 345 // (evict | delete) (list | range) 346 func (t *target) httpbckdelete(w http.ResponseWriter, r *http.Request, apireq *apiRequest) { 347 msg := aisMsg{} 348 if err := readJSON(w, r, &msg); err != nil { 349 return 350 } 351 if err := t.parseReq(w, r, apireq); err != nil { 352 return 353 } 354 if err := apireq.bck.Init(t.owner.bmd); err != nil { 355 if cmn.IsErrRemoteBckNotFound(err) { 356 t.BMDVersionFixup(r) 357 err = apireq.bck.Init(t.owner.bmd) 358 } 359 if err != nil { 360 t.writeErr(w, r, err) 361 return 362 } 363 } 364 365 switch msg.Action { 366 case apc.ActEvictRemoteBck: 367 keepMD := cos.IsParseBool(apireq.query.Get(apc.QparamKeepRemote)) 368 if keepMD { 369 nlp := newBckNLP(apireq.bck) 370 nlp.Lock() 371 defer nlp.Unlock() 372 373 core.UncacheBck(apireq.bck) 374 err := fs.DestroyBucket(msg.Action, apireq.bck.Bucket(), apireq.bck.Props.BID) 375 if err != nil { 376 t.writeErr(w, r, err) 377 return 378 } 379 // Recreate bucket directories (now empty), since bck is still in BMD 380 errs := fs.CreateBucket(apireq.bck.Bucket(), false /*nilbmd*/) 381 if len(errs) > 0 { 382 debug.AssertNoErr(errs[0]) 383 t.writeErr(w, r, errs[0]) // only 1 err is possible for 1 bck 384 } 385 } 386 case apc.ActDeleteObjects, apc.ActEvictObjects: 387 lrMsg := &apc.ListRange{} 388 if err := cos.MorphMarshal(msg.Value, lrMsg); err != nil { 389 t.writeErrf(w, r, cmn.FmtErrMorphUnmarshal, t.si, msg.Action, msg.Value, err) 390 return 391 } 392 // note extra safety check 393 for _, name := range lrMsg.ObjNames { 394 if !t.isValidObjname(w, r, name) { 395 return 396 } 397 } 398 rns := xreg.RenewEvictDelete(msg.UUID, msg.Action /*xaction kind*/, apireq.bck, lrMsg) 399 if rns.Err != nil { 400 t.writeErr(w, r, rns.Err) 401 return 402 } 403 xctn := rns.Entry.Get() 404 notif := &xact.NotifXact{ 405 Base: nl.Base{When: core.UponTerm, Dsts: []string{equalIC}, F: t.notifyTerm}, 406 Xact: xctn, 407 } 408 xctn.AddNotif(notif) 409 xact.GoRunW(xctn) 410 default: 411 t.writeErrAct(w, r, msg.Action) 412 } 413 } 414 415 // POST /v1/buckets/bucket-name 416 func (t *target) httpbckpost(w http.ResponseWriter, r *http.Request, apireq *apiRequest) { 417 msg, err := t.readAisMsg(w, r) 418 if err != nil { 419 return 420 } 421 if msg.Action != apc.ActPrefetchObjects { 422 t.writeErrAct(w, r, msg.Action) 423 return 424 } 425 if err := t.parseReq(w, r, apireq); err != nil { 426 return 427 } 428 // extra check 429 t.ensureLatestBMD(msg, r) 430 if err := apireq.bck.Init(t.owner.bmd); err != nil { 431 t.writeErr(w, r, err) 432 return 433 } 434 435 prfMsg := &apc.PrefetchMsg{} 436 if err := cos.MorphMarshal(msg.Value, prfMsg); err != nil { 437 t.writeErrf(w, r, cmn.FmtErrMorphUnmarshal, t.si, msg.Action, msg.Value, err) 438 return 439 } 440 if ecode, err := t.runPrefetch(msg.UUID, apireq.bck, prfMsg); err != nil { 441 t.writeErr(w, r, err, ecode) 442 } 443 } 444 445 // handle apc.ActPrefetchObjects <-- via api.Prefetch* and api.StartX* 446 func (t *target) runPrefetch(xactID string, bck *meta.Bck, prfMsg *apc.PrefetchMsg) (int, error) { 447 cs := fs.Cap() 448 if err := cs.Err(); err != nil { 449 return http.StatusInsufficientStorage, err 450 } 451 rns := xreg.RenewPrefetch(xactID, bck, prfMsg) 452 if rns.Err != nil { 453 return http.StatusBadRequest, rns.Err 454 } 455 456 xctn := rns.Entry.Get() 457 notif := &xact.NotifXact{ 458 Base: nl.Base{When: core.UponTerm, Dsts: []string{equalIC}, F: t.notifyTerm}, 459 Xact: xctn, 460 } 461 xctn.AddNotif(notif) 462 463 xact.GoRunW(xctn) 464 return 0, nil 465 } 466 467 // HEAD /v1/buckets/bucket-name 468 func (t *target) httpbckhead(w http.ResponseWriter, r *http.Request, apireq *apiRequest) { 469 var ( 470 bucketProps cos.StrKVs 471 err error 472 ctx = context.Background() 473 hdr = w.Header() 474 code int 475 ) 476 if err = t.parseReq(w, r, apireq); err != nil { 477 return 478 } 479 inBMD := true 480 if err = apireq.bck.Init(t.owner.bmd); err != nil { 481 if !cmn.IsErrRemoteBckNotFound(err) { // is ais 482 t.writeErr(w, r, err) 483 return 484 } 485 inBMD = false 486 } 487 if cmn.Rom.FastV(5, cos.SmoduleAIS) { 488 pid := apireq.query.Get(apc.QparamProxyID) 489 nlog.Infof("%s %s <= %s", r.Method, apireq.bck, pid) 490 } 491 492 debug.Assert(!apireq.bck.IsAIS()) 493 494 if apireq.bck.IsHTTP() { 495 originalURL := apireq.query.Get(apc.QparamOrigURL) 496 ctx = context.WithValue(ctx, cos.CtxOriginalURL, originalURL) 497 if !inBMD && originalURL == "" { 498 err = cmn.NewErrRemoteBckNotFound(apireq.bck.Bucket()) 499 t.writeErr(w, r, err, http.StatusNotFound, Silent) 500 return 501 } 502 } 503 // + cloud 504 bucketProps, code, err = t.Backend(apireq.bck).HeadBucket(ctx, apireq.bck) 505 if err != nil { 506 if !inBMD { 507 if code == http.StatusNotFound { 508 err = cmn.NewErrRemoteBckNotFound(apireq.bck.Bucket()) 509 t.writeErr(w, r, err, code, Silent) 510 } else { 511 err = cmn.NewErrFailedTo(t, "HEAD remote bucket", apireq.bck, err, code) 512 t._erris(w, r, cos.IsParseBool(apireq.query.Get(apc.QparamSilent)), err, code) 513 } 514 return 515 } 516 nlog.Warningf("%s: bucket %s, err: %v(%d)", t, apireq.bck, err, code) 517 bucketProps = make(cos.StrKVs) 518 bucketProps[apc.HdrBackendProvider] = apireq.bck.Provider 519 bucketProps[apc.HdrRemoteOffline] = strconv.FormatBool(apireq.bck.IsRemote()) 520 } 521 for k, v := range bucketProps { 522 if k == apc.HdrBucketVerEnabled && apireq.bck.Props != nil { 523 if curr := strconv.FormatBool(apireq.bck.VersionConf().Enabled); curr != v { 524 // e.g., change via vendor-provided CLI and similar 525 nlog.Errorf("%s: %s versioning got out of sync: %s != %s", t, apireq.bck, v, curr) 526 } 527 } 528 hdr.Set(k, v) 529 } 530 }