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  }