github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/api/bucket.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  	"net/http"
     9  	"net/url"
    10  	"strconv"
    11  
    12  	"github.com/NVIDIA/aistore/api/apc"
    13  	"github.com/NVIDIA/aistore/cmn"
    14  	"github.com/NVIDIA/aistore/cmn/cos"
    15  	"github.com/NVIDIA/aistore/cmn/debug"
    16  	jsoniter "github.com/json-iterator/go"
    17  )
    18  
    19  // SetBucketProps sets the properties of a bucket.
    20  // Validation of the properties passed in is performed by AIStore Proxy.
    21  func SetBucketProps(bp BaseParams, bck cmn.Bck, props *cmn.BpropsToSet) (string, error) {
    22  	b := cos.MustMarshal(apc.ActMsg{Action: apc.ActSetBprops, Value: props})
    23  	return patchBprops(bp, bck, b)
    24  }
    25  
    26  // ResetBucketProps resets the properties of a bucket to the global configuration.
    27  func ResetBucketProps(bp BaseParams, bck cmn.Bck) (string, error) {
    28  	b := cos.MustMarshal(apc.ActMsg{Action: apc.ActResetBprops})
    29  	return patchBprops(bp, bck, b)
    30  }
    31  
    32  func patchBprops(bp BaseParams, bck cmn.Bck, body []byte) (xid string, err error) {
    33  	bp.Method = http.MethodPatch
    34  	path := apc.URLPathBuckets.Join(bck.Name)
    35  	reqParams := AllocRp()
    36  	{
    37  		reqParams.BaseParams = bp
    38  		reqParams.Path = path
    39  		reqParams.Body = body
    40  		reqParams.Header = http.Header{cos.HdrContentType: []string{cos.ContentJSON}}
    41  		reqParams.Query = bck.NewQuery()
    42  	}
    43  	_, err = reqParams.doReqStr(&xid)
    44  	FreeRp(reqParams)
    45  	return
    46  }
    47  
    48  // HEAD(bucket): apc.HdrBucketProps => cmn.Bprops{} and apc.HdrBucketInfo => BucketInfo{}
    49  //
    50  // Converts the string type fields returned from the HEAD request to their
    51  // corresponding counterparts in the cmn.Bprops struct.
    52  //
    53  // By default, AIStore adds remote buckets to the cluster metadata on the fly.
    54  // Remote bucket that was never accessed before just "shows up" when user performs
    55  // HEAD, PUT, GET, SET-PROPS, and a variety of other operations.
    56  // This is done only once (and after confirming the bucket's existence and accessibility)
    57  // and doesn't require any action from the user.
    58  // Use `dontAddRemote` to override the default behavior: as the name implies, setting
    59  // `dontAddRemote = true` prevents AIS from adding remote bucket to the cluster's metadata.
    60  func HeadBucket(bp BaseParams, bck cmn.Bck, dontAddRemote bool) (p *cmn.Bprops, err error) {
    61  	var (
    62  		hdr    http.Header
    63  		path   = apc.URLPathBuckets.Join(bck.Name)
    64  		q      = make(url.Values, 4)
    65  		status int
    66  	)
    67  	if dontAddRemote {
    68  		q.Set(apc.QparamDontAddRemote, "true")
    69  	}
    70  	q = bck.AddToQuery(q)
    71  
    72  	bp.Method = http.MethodHead
    73  	reqParams := AllocRp()
    74  	{
    75  		reqParams.BaseParams = bp
    76  		reqParams.Path = path
    77  		reqParams.Query = q
    78  	}
    79  	if hdr, status, err = reqParams.doReqHdr(); err == nil {
    80  		p = &cmn.Bprops{}
    81  		err = jsoniter.Unmarshal([]byte(hdr.Get(apc.HdrBucketProps)), p)
    82  	} else {
    83  		err = hdr2msg(bck, status, err)
    84  	}
    85  	FreeRp(reqParams)
    86  	return
    87  }
    88  
    89  // fill-in herr message (HEAD response will never contain one)
    90  func hdr2msg(bck cmn.Bck, status int, err error) error {
    91  	herr, ok := err.(*cmn.ErrHTTP)
    92  	if !ok {
    93  		return err
    94  	}
    95  	debug.Assert(herr.Status == status, herr.Status, " vs ", status)
    96  
    97  	quoted := "\"" + bck.Cname("") + "\""
    98  	if !bck.IsQuery() && status == http.StatusNotFound {
    99  		herr.Message = "bucket " + quoted + " does not exist"
   100  		return herr
   101  	}
   102  	// common
   103  	herr.Message = "http error code '" + http.StatusText(status) + "'"
   104  	if status == http.StatusGone {
   105  		herr.Message += " (removed from the backend)"
   106  	}
   107  	herr.Message += ", bucket "
   108  	if bck.IsQuery() {
   109  		herr.Message += "query "
   110  	}
   111  	herr.Message += quoted
   112  	return herr
   113  }
   114  
   115  // CreateBucket sends request to create an AIS bucket with the given name and,
   116  // optionally, specific non-default properties (via cmn.BpropsToSet).
   117  //
   118  // See also:
   119  //   - github.com/NVIDIA/aistore/blob/main/docs/bucket.md#default-bucket-properties
   120  //   - cmn.BpropsToSet (cmn/api.go)
   121  //
   122  // Bucket properties can be also changed at any time via SetBucketProps (above).
   123  func CreateBucket(bp BaseParams, bck cmn.Bck, props *cmn.BpropsToSet, dontHeadRemote ...bool) error {
   124  	if err := bck.Validate(); err != nil {
   125  		return err
   126  	}
   127  	q := make(url.Values, 4)
   128  	if len(dontHeadRemote) > 0 && dontHeadRemote[0] {
   129  		q.Set(apc.QparamDontHeadRemote, "true")
   130  	}
   131  	bp.Method = http.MethodPost
   132  	reqParams := AllocRp()
   133  	{
   134  		reqParams.BaseParams = bp
   135  		reqParams.Path = apc.URLPathBuckets.Join(bck.Name)
   136  		reqParams.Body = cos.MustMarshal(apc.ActMsg{Action: apc.ActCreateBck, Value: props})
   137  		reqParams.Header = http.Header{cos.HdrContentType: []string{cos.ContentJSON}}
   138  		reqParams.Query = bck.AddToQuery(q)
   139  	}
   140  	err := reqParams.DoRequest()
   141  	FreeRp(reqParams)
   142  	return err
   143  }
   144  
   145  // DestroyBucket sends request to remove an AIS bucket with the given name.
   146  func DestroyBucket(bp BaseParams, bck cmn.Bck) error {
   147  	bp.Method = http.MethodDelete
   148  	reqParams := AllocRp()
   149  	{
   150  		reqParams.BaseParams = bp
   151  		reqParams.Path = apc.URLPathBuckets.Join(bck.Name)
   152  		reqParams.Body = cos.MustMarshal(apc.ActMsg{Action: apc.ActDestroyBck})
   153  		reqParams.Header = http.Header{cos.HdrContentType: []string{cos.ContentJSON}}
   154  		reqParams.Query = bck.NewQuery()
   155  	}
   156  	err := reqParams.DoRequest()
   157  	FreeRp(reqParams)
   158  	return err
   159  }
   160  
   161  // CopyBucket copies existing `bckFrom` bucket to the destination `bckTo` thus,
   162  // effectively, creating a copy of the `bckFrom`.
   163  //   - AIS will create `bckTo` on the fly but only if the destination bucket does not
   164  //     exist and _is_ provided by AIStore; 3rd party backend destination must exist -
   165  //     otherwise the copy operation won't be successful.
   166  //   - There are no limitations on copying buckets across Backend providers:
   167  //     you can copy AIS bucket to (or from) AWS bucket, and the latter to Google or Azure
   168  //     bucket, etc.
   169  //   - Copying multiple buckets to the same destination bucket is also permitted.
   170  //
   171  // `fltPresence` applies exclusively to remote `bckFrom` and is ignored if the source is ais://
   172  // The value is enum { apc.FltExists, apc.FltPresent, ... } - for complete enum, see api/apc/query.go
   173  // Namely:
   174  // * apc.FltExists        - copy all objects, including those that are not (present) in AIS
   175  // * apc.FltPresent 	  - copy the current `bckFrom` content in the cluster (default)
   176  // * apc.FltExistsOutside - copy only those remote objects that are not (present) in AIS
   177  //
   178  // msg.Prefix, if specified, applies always and regardless.
   179  //
   180  // Returns xaction ID if successful, an error otherwise. See also closely related api.ETLBucket
   181  func CopyBucket(bp BaseParams, bckFrom, bckTo cmn.Bck, msg *apc.CopyBckMsg, fltPresence ...int) (xid string, err error) {
   182  	if err = bckTo.Validate(); err != nil {
   183  		return
   184  	}
   185  	q := bckFrom.NewQuery()
   186  	_ = bckTo.AddUnameToQuery(q, apc.QparamBckTo)
   187  	if len(fltPresence) > 0 {
   188  		q.Set(apc.QparamFltPresence, strconv.Itoa(fltPresence[0]))
   189  	}
   190  	bp.Method = http.MethodPost
   191  	reqParams := AllocRp()
   192  	{
   193  		reqParams.BaseParams = bp
   194  		reqParams.Path = apc.URLPathBuckets.Join(bckFrom.Name)
   195  		reqParams.Body = cos.MustMarshal(apc.ActMsg{Action: apc.ActCopyBck, Value: msg})
   196  		reqParams.Header = http.Header{cos.HdrContentType: []string{cos.ContentJSON}}
   197  		reqParams.Query = q
   198  	}
   199  	_, err = reqParams.doReqStr(&xid)
   200  	FreeRp(reqParams)
   201  	return
   202  }
   203  
   204  // RenameBucket renames bckFrom as bckTo.
   205  // Returns xaction ID if successful, an error otherwise.
   206  func RenameBucket(bp BaseParams, bckFrom, bckTo cmn.Bck) (xid string, err error) {
   207  	if err = bckTo.Validate(); err != nil {
   208  		return
   209  	}
   210  	bp.Method = http.MethodPost
   211  	q := bckFrom.NewQuery()
   212  	_ = bckTo.AddUnameToQuery(q, apc.QparamBckTo)
   213  	reqParams := AllocRp()
   214  	{
   215  		reqParams.BaseParams = bp
   216  		reqParams.Path = apc.URLPathBuckets.Join(bckFrom.Name)
   217  		reqParams.Body = cos.MustMarshal(apc.ActMsg{Action: apc.ActMoveBck})
   218  		reqParams.Header = http.Header{cos.HdrContentType: []string{cos.ContentJSON}}
   219  		reqParams.Query = q
   220  	}
   221  	_, err = reqParams.doReqStr(&xid)
   222  	FreeRp(reqParams)
   223  	return
   224  }
   225  
   226  // EvictRemoteBucket sends request to evict an entire remote bucket from the AIStore
   227  // - keepMD: evict objects but keep bucket metadata
   228  func EvictRemoteBucket(bp BaseParams, bck cmn.Bck, keepMD bool) error {
   229  	var q url.Values
   230  	if keepMD {
   231  		q = url.Values{apc.QparamKeepRemote: []string{"true"}}
   232  	}
   233  
   234  	bp.Method = http.MethodDelete
   235  	reqParams := AllocRp()
   236  	{
   237  		reqParams.BaseParams = bp
   238  		reqParams.Path = apc.URLPathBuckets.Join(bck.Name)
   239  		reqParams.Body = cos.MustMarshal(apc.ActMsg{Action: apc.ActEvictRemoteBck})
   240  		reqParams.Header = http.Header{cos.HdrContentType: []string{cos.ContentJSON}}
   241  		reqParams.Query = bck.AddToQuery(q)
   242  	}
   243  	err := reqParams.DoRequest()
   244  	FreeRp(reqParams)
   245  	return err
   246  }
   247  
   248  // MakeNCopies starts an extended action (xaction) to bring a given bucket to a
   249  // certain redundancy level (num copies).
   250  // Returns xaction ID if successful, an error otherwise.
   251  func MakeNCopies(bp BaseParams, bck cmn.Bck, copies int) (xid string, err error) {
   252  	bp.Method = http.MethodPost
   253  	reqParams := AllocRp()
   254  	{
   255  		reqParams.BaseParams = bp
   256  		reqParams.Path = apc.URLPathBuckets.Join(bck.Name)
   257  		reqParams.Body = cos.MustMarshal(apc.ActMsg{Action: apc.ActMakeNCopies, Value: copies})
   258  		reqParams.Header = http.Header{cos.HdrContentType: []string{cos.ContentJSON}}
   259  		reqParams.Query = bck.NewQuery()
   260  	}
   261  	_, err = reqParams.doReqStr(&xid)
   262  	FreeRp(reqParams)
   263  	return
   264  }
   265  
   266  // Erasure-code entire `bck` bucket at a given `data`:`parity` redundancy.
   267  // The operation requires at least (`data + `parity` + 1) storage targets in the cluster.
   268  // Returns xaction ID if successful, an error otherwise.
   269  func ECEncodeBucket(bp BaseParams, bck cmn.Bck, data, parity int) (xid string, err error) {
   270  	bp.Method = http.MethodPost
   271  	// Without `string` conversion it makes base64 from []byte in `Body`.
   272  	ecConf := string(cos.MustMarshal(&cmn.ECConfToSet{
   273  		DataSlices:   &data,
   274  		ParitySlices: &parity,
   275  		Enabled:      apc.Ptr(true),
   276  	}))
   277  	reqParams := AllocRp()
   278  	{
   279  		reqParams.BaseParams = bp
   280  		reqParams.Path = apc.URLPathBuckets.Join(bck.Name)
   281  		reqParams.Body = cos.MustMarshal(apc.ActMsg{Action: apc.ActECEncode, Value: ecConf})
   282  		reqParams.Header = http.Header{cos.HdrContentType: []string{cos.ContentJSON}}
   283  		reqParams.Query = bck.NewQuery()
   284  	}
   285  	_, err = reqParams.doReqStr(&xid)
   286  	FreeRp(reqParams)
   287  	return
   288  }