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

     1  // Package cmn provides common constants, types, and utilities for AIS clients
     2  // and AIStore.
     3  /*
     4   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     5   */
     6  package cmn
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"reflect"
    12  	"sort"
    13  	"strings"
    14  
    15  	"github.com/NVIDIA/aistore/api/apc"
    16  	"github.com/NVIDIA/aistore/cmn/cos"
    17  	"github.com/NVIDIA/aistore/cmn/debug"
    18  	"github.com/NVIDIA/aistore/cmn/feat"
    19  	"github.com/NVIDIA/aistore/cmn/nlog"
    20  )
    21  
    22  // Bprops - manageable, user-configurable, and inheritable (from cluster config).
    23  // Includes per-bucket user-configurable checksum, version, LRU, erasure-coding, and more.
    24  //
    25  // At creation time, unless specified via api.CreateBucket, new bucket by default
    26  // inherits its properties from the global configuration.
    27  // * see api.CreateBucket for details
    28  // * for all inheritable props, see DefaultProps below
    29  //
    30  // Naming convention for setting/getting the particular props is defined as
    31  // joining the json tags with dot. Eg. when referring to `EC.Enabled` field
    32  // one would need to write `ec.enabled`. For more info refer to `IterFields`.
    33  
    34  const (
    35  	PropBucketAccessAttrs  = "access"             // Bucket access attributes.
    36  	PropBucketVerEnabled   = "versioning.enabled" // Enable/disable object versioning in a bucket.
    37  	PropBucketCreated      = "created"            // Bucket creation time.
    38  	PropBackendBck         = "backend_bck"
    39  	PropBackendBckName     = PropBackendBck + ".name"
    40  	PropBackendBckProvider = PropBackendBck + ".provider"
    41  )
    42  
    43  type (
    44  	Bprops struct {
    45  		BackendBck  Bck             `json:"backend_bck,omitempty"` // makes remote bucket out of a given ais bucket
    46  		Extra       ExtraProps      `json:"extra,omitempty" list:"omitempty"`
    47  		WritePolicy WritePolicyConf `json:"write_policy"`
    48  		Provider    string          `json:"provider" list:"readonly"`       // backend provider
    49  		Renamed     string          `list:"omit"`                           // non-empty if the bucket has been renamed
    50  		Cksum       CksumConf       `json:"checksum"`                       // the bucket's checksum
    51  		EC          ECConf          `json:"ec"`                             // erasure coding
    52  		LRU         LRUConf         `json:"lru"`                            // LRU (watermarks and enabled/disabled)
    53  		Mirror      MirrorConf      `json:"mirror"`                         // mirroring
    54  		Access      apc.AccessAttrs `json:"access,string"`                  // access permissions
    55  		Features    feat.Flags      `json:"features,string"`                // assorted features from feat.Bucket
    56  		BID         uint64          `json:"bid,string" list:"omit"`         // unique ID
    57  		Created     int64           `json:"created,string" list:"readonly"` // creation timestamp
    58  		Versioning  VersionConf     `json:"versioning"`                     // versioning (see "inherit")
    59  	}
    60  
    61  	ExtraProps struct {
    62  		AWS  ExtraPropsAWS  `json:"aws,omitempty" list:"omitempty"`
    63  		HTTP ExtraPropsHTTP `json:"http,omitempty" list:"omitempty"`
    64  		HDFS ExtraPropsHDFS `json:"hdfs,omitempty" list:"omitempty"` // NOTE: obsolete; rm with meta-version
    65  	}
    66  	ExtraToSet struct { // ref. bpropsFilterExtra
    67  		AWS  *ExtraPropsAWSToSet  `json:"aws"`
    68  		HTTP *ExtraPropsHTTPToSet `json:"http"`
    69  		HDFS *ExtraPropsHDFSToSet `json:"hdfs"` // ditto
    70  	}
    71  
    72  	ExtraPropsAWS struct {
    73  		CloudRegion string `json:"cloud_region,omitempty"`
    74  
    75  		// from https://github.com/aws/aws-sdk-go/blob/main/aws/config.go:
    76  		// - "An optional endpoint URL (hostname only or fully qualified URI)
    77  		// that overrides the default generated endpoint."
    78  		Endpoint string `json:"endpoint,omitempty"`
    79  
    80  		// from https://github.com/aws/aws-sdk-go/blob/main/aws/session/session.go:
    81  		// - "Overrides the config profile the Session should be created from. If not
    82  		// set the value of the environment variable will be loaded (AWS_PROFILE,
    83  		// or AWS_DEFAULT_PROFILE if the Shared Config is enabled)."
    84  		Profile string `json:"profile,omitempty"`
    85  
    86  		// Amazon S3: 1000
    87  		// - https://docs.aws.amazon.com/cli/latest/userguide/cli-usage-pagination.html#cli-usage-pagination-serverside
    88  		// vs OpenStack Swift: 10,000
    89  		// - https://docs.openstack.org/swift/latest/api/pagination.html
    90  		MaxPageSize int64 `json:"max_pagesize,omitempty"`
    91  	}
    92  	ExtraPropsAWSToSet struct {
    93  		CloudRegion *string `json:"cloud_region"`
    94  		Endpoint    *string `json:"endpoint"`
    95  		Profile     *string `json:"profile"`
    96  		MaxPageSize *int64  `json:"max_pagesize"`
    97  	}
    98  
    99  	ExtraPropsHTTP struct {
   100  		// Original URL prior to hashing.
   101  		OrigURLBck string `json:"original_url,omitempty" list:"readonly"`
   102  	}
   103  	ExtraPropsHTTPToSet struct {
   104  		OrigURLBck *string `json:"original_url"`
   105  	}
   106  
   107  	ExtraPropsHDFS struct {
   108  		// Reference directory.
   109  		RefDirectory string `json:"ref_directory,omitempty"`
   110  	}
   111  	ExtraPropsHDFSToSet struct {
   112  		RefDirectory *string `json:"ref_directory"`
   113  	}
   114  
   115  	// Once validated, BpropsToSet are copied to Bprops.
   116  	// The struct may have extra fields that do not exist in Bprops.
   117  	// Add tag 'copy:"skip"' to ignore those fields when copying values.
   118  	BpropsToSet struct {
   119  		BackendBck  *BackendBckToSet      `json:"backend_bck,omitempty"`
   120  		Versioning  *VersionConfToSet     `json:"versioning,omitempty"`
   121  		Cksum       *CksumConfToSet       `json:"checksum,omitempty"`
   122  		LRU         *LRUConfToSet         `json:"lru,omitempty"`
   123  		Mirror      *MirrorConfToSet      `json:"mirror,omitempty"`
   124  		EC          *ECConfToSet          `json:"ec,omitempty"`
   125  		Access      *apc.AccessAttrs      `json:"access,string,omitempty"`
   126  		Features    *feat.Flags           `json:"features,string,omitempty"`
   127  		WritePolicy *WritePolicyConfToSet `json:"write_policy,omitempty"`
   128  		Extra       *ExtraToSet           `json:"extra,omitempty"`
   129  		Force       bool                  `json:"force,omitempty" copy:"skip" list:"omit"`
   130  	}
   131  
   132  	BackendBckToSet struct {
   133  		Name     *string `json:"name"`
   134  		Provider *string `json:"provider"`
   135  	}
   136  )
   137  
   138  /////////////////
   139  // Bprops //
   140  /////////////////
   141  
   142  // By default, created buckets inherit their properties from the cluster (global) configuration.
   143  // Global configuration, in turn, is protected versioned, checksummed, and replicated across the entire cluster.
   144  //
   145  // * Bucket properties can be changed at any time via `api.SetBprops`.
   146  // * In addition, `api.CreateBucket` allows to specify (non-default) properties at bucket creation time.
   147  // * Inherited defaults include checksum, LRU, etc. configurations - see below.
   148  // * By default, LRU is disabled for AIS (`ais://`) buckets.
   149  //
   150  // See also:
   151  //   - github.com/NVIDIA/aistore/blob/main/docs/bucket.md#default-bucket-properties
   152  //   - BpropsToSet (above)
   153  //   - ais.defaultBckProps()
   154  func (bck *Bck) DefaultProps(c *ClusterConfig) *Bprops {
   155  	lru := c.LRU
   156  	if bck.IsAIS() {
   157  		lru.Enabled = false
   158  	}
   159  	cksum := c.Cksum
   160  	if cksum.Type == "" { // tests with empty cluster config
   161  		cksum.Type = cos.ChecksumXXHash
   162  	}
   163  	wp := c.WritePolicy
   164  	if wp.MD.IsImmediate() {
   165  		wp.MD = apc.WriteImmediate
   166  	}
   167  	if wp.Data.IsImmediate() {
   168  		wp.Data = apc.WriteImmediate
   169  	}
   170  	return &Bprops{
   171  		Cksum:       cksum,
   172  		LRU:         lru,
   173  		Mirror:      c.Mirror,
   174  		Versioning:  c.Versioning,
   175  		Access:      apc.AccessAll,
   176  		EC:          c.EC,
   177  		WritePolicy: wp,
   178  		Features:    c.Features,
   179  	}
   180  }
   181  
   182  func (bp *Bprops) SetProvider(provider string) {
   183  	debug.Assert(apc.IsProvider(provider))
   184  	bp.Provider = provider
   185  }
   186  
   187  func (bp *Bprops) Clone() *Bprops {
   188  	to := *bp
   189  	debug.Assert(bp.Equal(&to))
   190  	return &to
   191  }
   192  
   193  func (bp *Bprops) Equal(other *Bprops) (eq bool) {
   194  	src := *bp
   195  	src.BID = other.BID
   196  	src.Created = other.Created
   197  	eq = reflect.DeepEqual(&src, other)
   198  	return
   199  }
   200  
   201  func (bp *Bprops) Validate(targetCnt int) error {
   202  	debug.Assert(apc.IsProvider(bp.Provider))
   203  	if !bp.BackendBck.IsEmpty() {
   204  		if bp.Provider != apc.AIS {
   205  			return fmt.Errorf("wrong bucket provider %q: only AIS buckets can have remote backend (%q)",
   206  				bp.Provider, bp.BackendBck)
   207  		}
   208  		if bp.BackendBck.Provider == "" {
   209  			return fmt.Errorf("backend bucket %q: provider is empty", bp.BackendBck)
   210  		}
   211  		if bp.BackendBck.Name == "" {
   212  			return fmt.Errorf("backend bucket %q name is empty", bp.BackendBck)
   213  		}
   214  		if !bp.BackendBck.IsRemote() {
   215  			return fmt.Errorf("backend bucket %q must be remote", bp.BackendBck)
   216  		}
   217  	}
   218  
   219  	// run assorted props validators
   220  	var softErr error
   221  	for _, pv := range []PropsValidator{&bp.Cksum, &bp.Mirror, &bp.EC, &bp.Extra, &bp.WritePolicy} {
   222  		var err error
   223  		if pv == &bp.EC {
   224  			err = bp.EC.ValidateAsProps(targetCnt)
   225  		} else if pv == &bp.Extra {
   226  			err = bp.Extra.ValidateAsProps(bp.Provider)
   227  		} else {
   228  			err = pv.ValidateAsProps()
   229  		}
   230  		if err != nil {
   231  			if !IsErrSoft(err) {
   232  				return err
   233  			}
   234  			softErr = err
   235  		}
   236  	}
   237  	if bp.Mirror.Enabled && bp.EC.Enabled {
   238  		nlog.Warningln("n-way mirroring and EC are both enabled at the same time on the same bucket")
   239  	}
   240  
   241  	// not inheriting cluster-scope features
   242  	names := bp.Features.Names()
   243  	for _, n := range names {
   244  		if !feat.IsBucketScope(n) {
   245  			bp.Features = bp.Features.ClearName(n)
   246  		}
   247  	}
   248  	return softErr
   249  }
   250  
   251  func (bp *Bprops) Apply(propsToSet *BpropsToSet) {
   252  	err := copyProps(propsToSet, bp, apc.Daemon)
   253  	debug.AssertNoErr(err)
   254  }
   255  
   256  //
   257  // BpropsToSet
   258  //
   259  
   260  func NewBpropsToSet(nvs cos.StrKVs) (props *BpropsToSet, err error) {
   261  	props = &BpropsToSet{}
   262  	for key, val := range nvs {
   263  		name, value := strings.ToLower(key), val
   264  
   265  		// HACK: Some of the fields are present in `Bprops` and not in `BpropsToSet`.
   266  		// Thus, if user wants to change such field, `unknown field` will be returned.
   267  		// To make UX more friendly we attempt to set the value in an empty `Bprops` first.
   268  		if err := UpdateFieldValue(&Bprops{}, name, value); err != nil {
   269  			return props, err
   270  		}
   271  
   272  		if err := UpdateFieldValue(props, name, value); err != nil {
   273  			return props, err
   274  		}
   275  	}
   276  	return
   277  }
   278  
   279  func (c *ExtraProps) ValidateAsProps(arg ...any) error {
   280  	provider, ok := arg[0].(string)
   281  	debug.Assert(ok)
   282  	if provider == apc.HTTP && c.HTTP.OrigURLBck == "" {
   283  		return errors.New("original bucket URL must be set for a bucket with HTTP provider")
   284  	}
   285  	return nil
   286  }
   287  
   288  //
   289  // Bucket Summary - result for a given bucket, and all results -------------------------------------------------
   290  //
   291  
   292  type (
   293  	BsummResult struct {
   294  		Bck
   295  		apc.BsummResult
   296  	}
   297  	AllBsummResults []*BsummResult
   298  )
   299  
   300  // interface guard
   301  var _ sort.Interface = (*AllBsummResults)(nil)
   302  
   303  func (s AllBsummResults) Len() int           { return len(s) }
   304  func (s AllBsummResults) Less(i, j int) bool { return s[i].Bck.Less(&s[j].Bck) }
   305  func (s AllBsummResults) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
   306  
   307  func (s AllBsummResults) Aggregate(from *BsummResult) AllBsummResults {
   308  	for _, to := range s {
   309  		if to.Bck.Equal(&from.Bck) {
   310  			aggr(from, to)
   311  			return s
   312  		}
   313  	}
   314  	s = append(s, from)
   315  	return s
   316  }
   317  
   318  // across targets
   319  func aggr(from, to *BsummResult) {
   320  	if from.ObjSize.Min < to.ObjSize.Min {
   321  		to.ObjSize.Min = from.ObjSize.Min
   322  	}
   323  	if from.ObjSize.Max > to.ObjSize.Max {
   324  		to.ObjSize.Max = from.ObjSize.Max
   325  	}
   326  	to.ObjCount.Present += from.ObjCount.Present
   327  	to.ObjCount.Remote += from.ObjCount.Remote
   328  	to.TotalSize.OnDisk += from.TotalSize.OnDisk
   329  	to.TotalSize.PresentObjs += from.TotalSize.PresentObjs
   330  	to.TotalSize.RemoteObjs += from.TotalSize.RemoteObjs
   331  }
   332  
   333  func (s AllBsummResults) Finalize(dsize map[string]uint64, testingEnv bool) {
   334  	var totalDisksSize uint64
   335  	for _, tsiz := range dsize {
   336  		totalDisksSize += tsiz
   337  		if testingEnv {
   338  			break
   339  		}
   340  	}
   341  	for _, summ := range s {
   342  		if summ.ObjCount.Present > 0 {
   343  			summ.ObjSize.Avg = int64(cos.DivRoundU64(summ.TotalSize.PresentObjs, summ.ObjCount.Present))
   344  		}
   345  		summ.UsedPct = cos.DivRoundU64(summ.TotalSize.OnDisk*100, totalDisksSize)
   346  	}
   347  }
   348  
   349  //
   350  // Multi-object (list|range) operations source bucket => dest. bucket ---------------------------------------
   351  //
   352  
   353  type (
   354  	// ArchiveBckMsg contains parameters to archive mutiple objects from the specified (source) bucket.
   355  	// Destination bucket may the same as the source or a different one.
   356  	// --------------------  NOTE on terminology:   ---------------------
   357  	// "archive" is any (.tar, .tgz/.tar.gz, .zip, .tar.lz4) formatted object often also called "shard"
   358  	//
   359  	// See also: apc.PutApndArchArgs
   360  	ArchiveBckMsg struct {
   361  		ToBck Bck `json:"tobck"`
   362  		apc.ArchiveMsg
   363  	}
   364  
   365  	//  Multi-object copy & transform (see also: TCBMsg)
   366  	TCObjsMsg struct {
   367  		ToBck Bck `json:"tobck"`
   368  		apc.TCObjsMsg
   369  	}
   370  )
   371  
   372  func (msg *ArchiveBckMsg) Cname() string { return msg.ToBck.Cname(msg.ArchName) }