github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmn/err.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  	"bytes"
    10  	"errors"
    11  	"fmt"
    12  	"io/fs"
    13  	"net/http"
    14  	"path/filepath"
    15  	"runtime"
    16  	"strconv"
    17  	"strings"
    18  	"sync"
    19  
    20  	"github.com/NVIDIA/aistore/api/apc"
    21  	"github.com/NVIDIA/aistore/cmn/cos"
    22  	"github.com/NVIDIA/aistore/cmn/debug"
    23  	"github.com/NVIDIA/aistore/cmn/nlog"
    24  	jsoniter "github.com/json-iterator/go"
    25  )
    26  
    27  // This source contains common AIS node inter-module errors -
    28  // the errors that some AIS packages (within a given running AIS node) return
    29  // and other AIS packages handle.
    30  
    31  const (
    32  	stackTracePrefix = "stack: ["
    33  
    34  	fmtErrBckName   = "bucket name %q is invalid: " + cos.OnlyPlus
    35  	fmtErrNamespace = "bucket namespace (uuid: %q, name: %q) " + cos.OnlyNice
    36  
    37  	FmtErrIntegrity      = "[%s%d, for troubleshooting see %s/blob/main/docs/troubleshooting.md]"
    38  	FmtErrUnmarshal      = "%s: failed to unmarshal %s (%s), err: %w"
    39  	FmtErrMorphUnmarshal = "%s: failed to unmarshal %s (%T), err: %w"
    40  	FmtErrUnknown        = "%s: unknown %s %q"
    41  	FmtErrBackwardCompat = "%v (backward compatibility is supported only one version back, e.g. 3.9 => 3.10)"
    42  
    43  	fmtErrFailedTo = "%s: failed to %s %s, err: %v" // (ErrFailedTo)
    44  
    45  	BadSmapPrefix = "[bad cluster map]"
    46  
    47  	StartupMayTimeout = "cluster startup is taking unusually long time..." // related ErrStartupTimeout
    48  )
    49  
    50  // API error structure
    51  // is returned to aistore client and carries one of the specific errors enumerated below
    52  type (
    53  	ErrHTTP struct {
    54  		TypeCode   string `json:"tcode,omitempty"`
    55  		Message    string `json:"message"`
    56  		Method     string `json:"method"`
    57  		URLPath    string `json:"url_path"`
    58  		RemoteAddr string `json:"remote_addr"`
    59  		Caller     string `json:"caller"`
    60  		Node       string `json:"node"`
    61  		trace      []byte
    62  		Status     int `json:"status"`
    63  	}
    64  )
    65  
    66  // assorted aistore errors
    67  type (
    68  	ErrBucketAlreadyExists struct{ bck Bck }
    69  	ErrRemoteBckNotFound   struct{ bck Bck }
    70  	ErrRemoteBucketOffline struct{ bck Bck }
    71  	ErrBckNotFound         struct{ bck Bck }
    72  
    73  	ErrBusy struct {
    74  		whereOrType string
    75  		what        string
    76  		detail      []string
    77  	}
    78  
    79  	ErrFailedTo struct {
    80  		actor  string // most of the time it's this (target|proxy) node but may also be some other "actor"
    81  		what   any    // not necessarily LOM
    82  		err    error  // original error that can be Unwrap-ed
    83  		action string // not necessarily msg.Action
    84  		status int    // http status, if available
    85  	}
    86  	ErrUnsupp struct {
    87  		action, what string
    88  	}
    89  	ErrNotImpl struct {
    90  		action, what string
    91  	}
    92  
    93  	ErrInvalidBackendProvider struct {
    94  		bck Bck
    95  	}
    96  	ErrCapExceeded struct {
    97  		totalBytes     uint64
    98  		totalBytesUsed uint64
    99  		highWM         int64
   100  		cleanupWM      int64
   101  		usedPct        int32
   102  		oos            bool
   103  	}
   104  	ErrBucketAccessDenied struct{ errAccessDenied }
   105  	ErrObjectAccessDenied struct{ errAccessDenied }
   106  	errAccessDenied       struct {
   107  		entity      string
   108  		operation   string
   109  		accessAttrs apc.AccessAttrs
   110  	}
   111  
   112  	ErrInvalidCksum struct {
   113  		expectedHash string
   114  		actualHash   string
   115  	}
   116  
   117  	ErrMountpathNotFound struct {
   118  		mpath    string
   119  		fqn      string
   120  		disabled bool
   121  	}
   122  	ErrInvalidMountpath struct {
   123  		mpath string
   124  		cause string
   125  	}
   126  	ErrInvalidFSPathsConf struct {
   127  		err error
   128  	}
   129  
   130  	ErrNoNodes struct {
   131  		role    string
   132  		mmcount int // maintenance mode
   133  	}
   134  	ErrXactNotFound struct {
   135  		cause string
   136  	}
   137  	ErrObjDefunct struct {
   138  		name   string // object's name
   139  		d1, d2 uint64 // lom.md.(bucket-ID) and lom.bck.(bucket-ID), respectively
   140  	}
   141  	ErrAborted struct {
   142  		err  error
   143  		what string
   144  		ctx  string
   145  	}
   146  	ErrInitBackend struct {
   147  		Provider string
   148  	}
   149  	ErrMissingBackend struct {
   150  		Provider string
   151  		Msg      string
   152  	}
   153  	ErrETL struct {
   154  		Reason string
   155  		ETLErrCtx
   156  	}
   157  	ETLErrCtx struct {
   158  		TID     string
   159  		ETLName string
   160  		PodName string
   161  		SvcName string
   162  	}
   163  	ErrSoft struct {
   164  		what string
   165  	}
   166  
   167  	ErrLmetaCorrupted struct {
   168  		err error
   169  	}
   170  	ErrLmetaNotFound struct {
   171  		err error
   172  	}
   173  
   174  	ErrLimitedCoexistence struct {
   175  		node    string // this (local) node
   176  		xaction string
   177  		action  string
   178  		detail  string
   179  	}
   180  	ErrXactUsePrev struct { // equivalent to xreg.WprUse
   181  		xaction string
   182  	}
   183  	ErrXactTgtInMaint struct {
   184  		xaction string
   185  		tname   string
   186  	}
   187  	ErrStreamTerminated struct {
   188  		err    error
   189  		stream string
   190  		reason string
   191  		detail string
   192  	}
   193  	ErrInvalidObjName struct {
   194  		name string
   195  	}
   196  	ErrNotRemoteBck struct {
   197  		act string
   198  		bck *Bck
   199  	}
   200  	ErrRangeNotSatisfiable struct {
   201  		err    error    // original (backend reported) error
   202  		ranges []string // RFC 7233
   203  		size   int64    // [0, size)
   204  	}
   205  )
   206  
   207  var (
   208  	thisNodeName string
   209  	cleanPathErr func(error)
   210  )
   211  
   212  func InitErrs(a string, b func(error)) { thisNodeName, cleanPathErr = a, b }
   213  
   214  var (
   215  	ErrSkip             = errors.New("skip")
   216  	ErrStartupTimeout   = errors.New("startup timeout") // related StartupMayTimeout
   217  	ErrQuiesceTimeout   = errors.New("timed out waiting for quiescence")
   218  	ErrNotEnoughTargets = errors.New("not enough target nodes")
   219  	ErrNoMountpaths     = errors.New("no mountpaths")
   220  
   221  	// aborts
   222  	ErrXactRenewAbort   = errors.New("renewal abort")
   223  	ErrXactUserAbort    = errors.New("user abort")              // via apc.ActXactStop
   224  	ErrXactICNotifAbort = errors.New("IC(notifications) abort") // ditto
   225  )
   226  
   227  // ErrFailedTo
   228  
   229  func NewErrFailedTo(actor fmt.Stringer, action string, what any, err error, ecode ...int) *ErrFailedTo {
   230  	if e, ok := err.(*ErrFailedTo); ok {
   231  		return e
   232  	}
   233  	_clean(err)
   234  
   235  	e := &ErrFailedTo{action: action, what: what, err: err}
   236  	e.actor = thisNodeName
   237  	if actor != nil {
   238  		e.actor = actor.String()
   239  	}
   240  	if len(ecode) > 0 {
   241  		e.status = ecode[0]
   242  		if err == nil && e.status > 0 {
   243  			e.err = errors.New("error code: " + strconv.Itoa(e.status) + "(\"" + http.StatusText(e.status) + "\")")
   244  		}
   245  	}
   246  	return e
   247  }
   248  
   249  func (e *ErrFailedTo) Error() string {
   250  	return fmt.Sprintf(fmtErrFailedTo, e.actor, e.action, e.what, e.err)
   251  }
   252  
   253  func (e *ErrFailedTo) Unwrap() (err error) { return e.err }
   254  
   255  // ErrStreamTerminated
   256  
   257  func NewErrStreamTerminated(stream string, err error, reason, detail string) *ErrStreamTerminated {
   258  	return &ErrStreamTerminated{stream: stream, err: err, reason: reason, detail: detail}
   259  }
   260  
   261  func (e *ErrStreamTerminated) Error() string {
   262  	return fmt.Sprintf("%s terminated(%q, %v): %s", e.stream, e.reason, e.err, e.detail)
   263  }
   264  
   265  func (e *ErrStreamTerminated) Unwrap() (err error) { return e.err }
   266  
   267  func IsErrStreamTerminated(err error) bool {
   268  	_, ok := err.(*ErrStreamTerminated)
   269  	return ok
   270  }
   271  
   272  // ErrUnsupp & ErrNotImpl
   273  
   274  func NewErrUnsupp(action, what string) *ErrUnsupp { return &ErrUnsupp{action, what} }
   275  
   276  func (e *ErrUnsupp) Error() string {
   277  	return fmt.Sprintf("cannot %s %s - operation not supported", e.action, e.what)
   278  }
   279  
   280  func isErrUnsupp(err error) bool {
   281  	_, ok := err.(*ErrUnsupp)
   282  	return ok
   283  }
   284  
   285  func NewErrNotImpl(action, what string) *ErrNotImpl { return &ErrNotImpl{action, what} }
   286  
   287  func (e *ErrNotImpl) Error() string {
   288  	return fmt.Sprintf("cannot %s %s - not impemented yet", e.action, e.what)
   289  }
   290  
   291  func isErrNotImpl(err error) bool {
   292  	_, ok := err.(*ErrNotImpl)
   293  	return ok
   294  }
   295  
   296  // (ais) ErrBucketAlreadyExists
   297  
   298  func NewErrBckAlreadyExists(bck *Bck) *ErrBucketAlreadyExists {
   299  	return &ErrBucketAlreadyExists{bck: *bck}
   300  }
   301  
   302  func (e *ErrBucketAlreadyExists) Error() string {
   303  	return fmt.Sprintf("bucket %q already exists", e.bck)
   304  }
   305  
   306  func IsErrBucketAlreadyExists(err error) bool {
   307  	_, ok := err.(*ErrBucketAlreadyExists)
   308  	return ok
   309  }
   310  
   311  // remote ErrRemoteBckNotFound (compare with ErrBckNotFound)
   312  
   313  func NewErrRemoteBckNotFound(bck *Bck) *ErrRemoteBckNotFound {
   314  	return &ErrRemoteBckNotFound{bck: *bck}
   315  }
   316  
   317  func (e *ErrRemoteBckNotFound) Error() string {
   318  	if e.bck.IsCloud() {
   319  		return fmt.Sprintf("%s bucket %q does not exist", apc.NormalizeProvider(e.bck.Provider), e.bck.Cname(""))
   320  	}
   321  	return fmt.Sprintf("remote bucket %q does not exist", e.bck)
   322  }
   323  
   324  func IsErrRemoteBckNotFound(err error) bool {
   325  	_, ok := err.(*ErrRemoteBckNotFound)
   326  	return ok
   327  }
   328  
   329  // ErrBckNotFound - applies to ais buckets exclusively
   330  // (compare with ErrRemoteBckNotFound)
   331  
   332  func NewErrBckNotFound(bck *Bck) *ErrBckNotFound {
   333  	return &ErrBckNotFound{bck: *bck}
   334  }
   335  
   336  func (e *ErrBckNotFound) Error() string {
   337  	return fmt.Sprintf("bucket %q does not exist", e.bck)
   338  }
   339  
   340  func IsErrBckNotFound(err error) bool {
   341  	_, ok := err.(*ErrBckNotFound)
   342  	return ok
   343  }
   344  
   345  // ErrRemoteBucketOffline
   346  
   347  func NewErrRemoteBckOffline(bck *Bck) *ErrRemoteBucketOffline {
   348  	return &ErrRemoteBucketOffline{bck: *bck}
   349  }
   350  
   351  func (e *ErrRemoteBucketOffline) Error() string {
   352  	return fmt.Sprintf("bucket %q is currently unreachable", e.bck)
   353  }
   354  
   355  func isErrRemoteBucketOffline(err error) bool {
   356  	_, ok := err.(*ErrRemoteBucketOffline)
   357  	return ok
   358  }
   359  
   360  // ErrInvalidBackendProvider
   361  
   362  func (e *ErrInvalidBackendProvider) Error() string {
   363  	if e.bck.Name != "" {
   364  		return fmt.Sprintf("invalid backend provider %q for bucket %s: must be one of [%s]",
   365  			e.bck.Provider, e.bck, apc.AllProviders)
   366  	}
   367  	return fmt.Sprintf("invalid backend provider %q: must be one of [%s]", e.bck.Provider, apc.AllProviders)
   368  }
   369  
   370  func (*ErrInvalidBackendProvider) Is(target error) bool {
   371  	_, ok := target.(*ErrInvalidBackendProvider)
   372  	return ok
   373  }
   374  
   375  // ErrBusy
   376  
   377  func NewErrBusy(whereOrType, what string, detail ...string) *ErrBusy {
   378  	return &ErrBusy{whereOrType, what, detail}
   379  }
   380  
   381  func (e *ErrBusy) Error() string {
   382  	var s string
   383  	if len(e.detail) > 0 {
   384  		s = " (" + e.detail[0] + ")"
   385  	}
   386  	return fmt.Sprintf("%s %q is currently busy%s, please try again", e.whereOrType, e.what, s)
   387  }
   388  
   389  // errAccessDenied & ErrBucketAccessDenied
   390  
   391  func (e *errAccessDenied) String() string {
   392  	return fmt.Sprintf("%s: %s access denied (allowed: [%s])",
   393  		e.entity, e.operation, e.accessAttrs.Describe(false /*include all*/))
   394  }
   395  
   396  func (e *ErrBucketAccessDenied) Error() string {
   397  	return "bucket " + e.String()
   398  }
   399  
   400  func NewBucketAccessDenied(bucket, oper string, aattrs apc.AccessAttrs) *ErrBucketAccessDenied {
   401  	return &ErrBucketAccessDenied{errAccessDenied{bucket, oper, aattrs}}
   402  }
   403  
   404  func (e *ErrObjectAccessDenied) Error() string {
   405  	return "object " + e.String()
   406  }
   407  
   408  func NewObjectAccessDenied(object, oper string, aattrs apc.AccessAttrs) *ErrObjectAccessDenied {
   409  	return &ErrObjectAccessDenied{errAccessDenied{object, oper, aattrs}}
   410  }
   411  
   412  // ErrCapExceeded
   413  
   414  func NewErrCapExceeded(totalBytesUsed, totalBytes uint64, highWM, cleanupWM int64, usedPct int32, oos bool) *ErrCapExceeded {
   415  	return &ErrCapExceeded{
   416  		totalBytes:     totalBytes, // avail + used
   417  		totalBytesUsed: totalBytesUsed,
   418  		highWM:         highWM,
   419  		cleanupWM:      cleanupWM,
   420  		usedPct:        usedPct,
   421  		oos:            oos,
   422  	}
   423  }
   424  
   425  func (e *ErrCapExceeded) Error() string {
   426  	suffix := fmt.Sprintf("total used %s out of %s", cos.ToSizeIEC(int64(e.totalBytesUsed), 2),
   427  		cos.ToSizeIEC(int64(e.totalBytes), 2))
   428  	if e.oos {
   429  		return fmt.Sprintf("out of space: used %d%% of total capacity on at least one of the mountpaths (%s)",
   430  			e.usedPct, suffix)
   431  	}
   432  	if e.highWM == 0 {
   433  		debug.Assert(e.cleanupWM > 0)
   434  		return fmt.Sprintf("low on free space: used capacity %d%% exceeded cleanup watermark(%d%%) (%s)",
   435  			e.usedPct, e.cleanupWM, suffix)
   436  	}
   437  	debug.Assert(e.highWM > 0)
   438  	return fmt.Sprintf("low on free space: used capacity %d%% exceeded high watermark(%d%%) (%s)",
   439  		e.usedPct, e.highWM, suffix)
   440  }
   441  
   442  func IsErrCapExceeded(err error) bool {
   443  	_, ok := err.(*ErrCapExceeded)
   444  	return ok
   445  }
   446  
   447  // ErrInvalidCksum
   448  
   449  func (e *ErrInvalidCksum) Error() string {
   450  	return fmt.Sprintf("checksum: expected [%s], actual [%s]", e.expectedHash, e.actualHash)
   451  }
   452  
   453  func NewErrInvalidCksum(eHash, aHash string) *ErrInvalidCksum {
   454  	return &ErrInvalidCksum{actualHash: aHash, expectedHash: eHash}
   455  }
   456  
   457  func (e *ErrInvalidCksum) Expected() string { return e.expectedHash }
   458  
   459  // ErrMountpathNotFound
   460  
   461  func (e *ErrMountpathNotFound) Error() string {
   462  	if e.mpath != "" {
   463  		if e.disabled {
   464  			return "mountpath " + e.mpath + " is disabled"
   465  		}
   466  		return "mountpath " + e.mpath + " does not exist"
   467  	}
   468  	debug.Assert(e.fqn != "")
   469  	if e.disabled {
   470  		return "mountpath for fqn " + e.fqn + " is disabled"
   471  	}
   472  	return "mountpath for fqn " + e.fqn + " does not exist"
   473  }
   474  
   475  func NewErrMountpathNotFound(mpath, fqn string, disabled bool) *ErrMountpathNotFound {
   476  	return &ErrMountpathNotFound{mpath: mpath, fqn: fqn, disabled: disabled}
   477  }
   478  
   479  func IsErrMountpathNotFound(err error) bool {
   480  	_, ok := err.(*ErrMountpathNotFound)
   481  	return ok
   482  }
   483  
   484  // ErrInvalidMountpath
   485  
   486  func (e *ErrInvalidMountpath) Error() string {
   487  	return "invalid mountpath [" + e.mpath + "]; " + e.cause
   488  }
   489  
   490  func NewErrInvalidaMountpath(mpath, cause string) *ErrInvalidMountpath {
   491  	return &ErrInvalidMountpath{mpath: mpath, cause: cause}
   492  }
   493  
   494  // ErrInvalidFSPathsConf
   495  
   496  func NewErrInvalidFSPathsConf(err error) *ErrInvalidFSPathsConf {
   497  	return &ErrInvalidFSPathsConf{err}
   498  }
   499  
   500  func (e *ErrInvalidFSPathsConf) Unwrap() (err error) { return e.err }
   501  
   502  func (e *ErrInvalidFSPathsConf) Error() string {
   503  	return fmt.Sprintf("invalid \"fspaths\" configuration: %v", e.err)
   504  }
   505  
   506  // ErrNoNodes
   507  
   508  func NewErrNoNodes(role string, mmcount int) *ErrNoNodes {
   509  	return &ErrNoNodes{role: role, mmcount: mmcount}
   510  }
   511  
   512  func (e *ErrNoNodes) Error() (s string) {
   513  	var what string
   514  	if e.role == apc.Proxy {
   515  		what = "gateway"
   516  		s = "no proxies (gateways) in the cluster"
   517  	} else {
   518  		debug.Assert(e.role == apc.Target)
   519  		what = "target"
   520  		s = "no storage targets in the cluster"
   521  	}
   522  	if e.mmcount > 0 {
   523  		s += fmt.Sprintf(" (%d %s%s in maintenance mode or being decommissioned)",
   524  			e.mmcount, what, cos.Plural(e.mmcount))
   525  	}
   526  	return
   527  }
   528  
   529  // ErrXactNotFound
   530  
   531  func (e *ErrXactNotFound) Error() string {
   532  	return "xaction " + e.cause + " not found"
   533  }
   534  
   535  func NewErrXactNotFoundError(cause string) *ErrXactNotFound {
   536  	return &ErrXactNotFound{cause: cause}
   537  }
   538  
   539  func IsErrXactNotFound(err error) bool {
   540  	_, ok := err.(*ErrXactNotFound)
   541  	return ok
   542  }
   543  
   544  // ErrObjDefunct
   545  
   546  func (e *ErrObjDefunct) Error() string {
   547  	return fmt.Sprintf("%s is defunct (%d != %d)", e.name, e.d1, e.d2)
   548  }
   549  
   550  func NewErrObjDefunct(name string, d1, d2 uint64) *ErrObjDefunct {
   551  	return &ErrObjDefunct{name, d1, d2}
   552  }
   553  
   554  func isErrObjDefunct(err error) bool {
   555  	_, ok := err.(*ErrObjDefunct)
   556  	return ok
   557  }
   558  
   559  // ErrAborted
   560  
   561  func NewErrAborted(what, ctx string, err error) *ErrAborted {
   562  	if e, ok := err.(*ErrAborted); ok {
   563  		return e
   564  	}
   565  	_clean(err)
   566  	return &ErrAborted{what: what, ctx: ctx, err: err}
   567  }
   568  
   569  func (e *ErrAborted) Error() (s string) {
   570  	s = e.what + " aborted"
   571  	if e.err != nil {
   572  		s = fmt.Sprintf("%s, err: %v", s, e.err)
   573  	}
   574  	if e.ctx != "" {
   575  		s += " (" + e.ctx + ")"
   576  	}
   577  	return
   578  }
   579  
   580  func (e *ErrAborted) Unwrap() (err error) { return e.err }
   581  
   582  func IsErrAborted(err error) bool { return AsErrAborted(err) != nil }
   583  
   584  func AsErrAborted(err error) (errAborted *ErrAborted) {
   585  	var ok bool
   586  	if errAborted, ok = err.(*ErrAborted); ok {
   587  		return
   588  	}
   589  	target := &ErrAborted{}
   590  	if errors.As(err, &target) {
   591  		errAborted = target
   592  	}
   593  	return
   594  }
   595  
   596  // ErrInitBackend & ErrMissingBackend
   597  
   598  func (e *ErrInitBackend) Error() string {
   599  	return fmt.Sprintf(
   600  		"cannot initialize %q backend (present in the cluster configuration): missing %s-supporting libraries in the build",
   601  		e.Provider, e.Provider,
   602  	)
   603  }
   604  
   605  func (e *ErrMissingBackend) Error() string {
   606  	if e.Msg != "" {
   607  		return e.Msg
   608  	}
   609  	return fmt.Sprintf("%q backend is missing in the cluster configuration", e.Provider)
   610  }
   611  
   612  // ErrETL
   613  
   614  func NewErrETL(ctx *ETLErrCtx, format string, a ...any) *ErrETL {
   615  	e := &ErrETL{
   616  		Reason: fmt.Sprintf(format, a...),
   617  	}
   618  	return e.WithContext(ctx)
   619  }
   620  
   621  func (e *ErrETL) Error() string {
   622  	s := make([]string, 0, 3)
   623  	if e.TID != "" {
   624  		s = append(s, fmt.Sprintf("t[%s]", e.TID))
   625  	}
   626  	if e.ETLName != "" {
   627  		s = append(s, fmt.Sprintf("etl=%q", e.ETLName))
   628  	}
   629  	if e.PodName != "" {
   630  		s = append(s, fmt.Sprintf("pod=%q", e.PodName))
   631  	}
   632  	if e.SvcName != "" {
   633  		s = append(s, fmt.Sprintf("service=%q", e.SvcName))
   634  	}
   635  	return fmt.Sprintf("[%s] %s", strings.Join(s, ","), e.Reason)
   636  }
   637  
   638  func (e *ErrETL) withTarget(tid string) *ErrETL {
   639  	if tid != "" {
   640  		e.TID = tid
   641  	}
   642  	return e
   643  }
   644  
   645  func (e *ErrETL) withETLName(name string) *ErrETL {
   646  	if name != "" {
   647  		e.ETLName = name
   648  	}
   649  	return e
   650  }
   651  
   652  func (e *ErrETL) withSvcName(name string) *ErrETL {
   653  	if name != "" {
   654  		e.SvcName = name
   655  	}
   656  	return e
   657  }
   658  
   659  func (e *ErrETL) WithPodName(name string) *ErrETL {
   660  	if name != "" {
   661  		e.PodName = name
   662  	}
   663  	return e
   664  }
   665  
   666  func (e *ErrETL) WithContext(ctx *ETLErrCtx) *ErrETL {
   667  	if ctx == nil {
   668  		return e
   669  	}
   670  	return e.
   671  		withTarget(ctx.TID).
   672  		WithPodName(ctx.PodName).
   673  		withETLName(ctx.ETLName).
   674  		withSvcName(ctx.SvcName)
   675  }
   676  
   677  // ErrSoft
   678  // non-critical and can be ignored in certain cases (e.g, when `--force` is set)
   679  
   680  func NewErrSoft(what string) *ErrSoft {
   681  	return &ErrSoft{what}
   682  }
   683  
   684  func (e *ErrSoft) Error() string {
   685  	return e.what
   686  }
   687  
   688  func IsErrSoft(err error) bool {
   689  	if _, ok := err.(*ErrSoft); ok {
   690  		return true
   691  	}
   692  	target := &ErrSoft{}
   693  	return errors.As(err, &target)
   694  }
   695  
   696  // ErrLmetaCorrupted & ErrLmetaNotFound
   697  
   698  func NewErrLmetaCorrupted(err error) *ErrLmetaCorrupted { return &ErrLmetaCorrupted{err} }
   699  func (e *ErrLmetaCorrupted) Error() string              { return e.err.Error() }
   700  func (e *ErrLmetaCorrupted) Unwrap() (err error)        { return e.err }
   701  
   702  func IsErrLmetaCorrupted(err error) bool {
   703  	_, ok := err.(*ErrLmetaCorrupted)
   704  	return ok
   705  }
   706  
   707  func NewErrLmetaNotFound(err error) *ErrLmetaNotFound { return &ErrLmetaNotFound{err} }
   708  func (e *ErrLmetaNotFound) Error() string             { return e.err.Error() }
   709  func (e *ErrLmetaNotFound) Unwrap() (err error)       { return e.err }
   710  
   711  func IsErrLmetaNotFound(err error) bool {
   712  	_, ok := err.(*ErrLmetaNotFound)
   713  	return ok
   714  }
   715  
   716  // ErrLimitedCoexistence
   717  
   718  func NewErrLimitedCoexistence(node, xaction, action, detail string) *ErrLimitedCoexistence {
   719  	return &ErrLimitedCoexistence{node, xaction, action, detail}
   720  }
   721  
   722  func (e *ErrLimitedCoexistence) Error() string {
   723  	return fmt.Sprintf("%s: %s is currently running, cannot run %q(%s) concurrently",
   724  		e.node, e.xaction, e.action, e.detail)
   725  }
   726  
   727  // ErrXactUsePrev
   728  
   729  func NewErrXactUsePrev(xaction string) *ErrXactUsePrev {
   730  	return &ErrXactUsePrev{xaction}
   731  }
   732  
   733  func (e *ErrXactUsePrev) Error() string {
   734  	return e.xaction + "is already running - not starting"
   735  }
   736  
   737  func IsErrXactUsePrev(err error) bool {
   738  	_, ok := err.(*ErrXactUsePrev)
   739  	return ok
   740  }
   741  
   742  // ErrInvalidObjName
   743  
   744  func ValidateObjName(name string) (err *ErrInvalidObjName) {
   745  	if cos.IsLastB(name, filepath.Separator) || strings.Contains(name, "../") {
   746  		err = &ErrInvalidObjName{name}
   747  	}
   748  	return err
   749  }
   750  
   751  func (e *ErrInvalidObjName) Error() string {
   752  	return fmt.Sprintf("invalid object name %q", e.name)
   753  }
   754  
   755  // ErrNotRemoteBck
   756  
   757  func ValidateRemoteBck(act string, bck *Bck) (err *ErrNotRemoteBck) {
   758  	if !bck.IsRemote() {
   759  		err = &ErrNotRemoteBck{act, bck}
   760  	}
   761  	return err
   762  }
   763  
   764  func (e *ErrNotRemoteBck) Error() string {
   765  	return fmt.Sprintf("%s: expecting remote bucket (have %s)", e.act, e.bck)
   766  }
   767  
   768  // ErrXactTgtInMaint
   769  
   770  func NewErrXactTgtInMaint(xaction, tname string) *ErrXactTgtInMaint {
   771  	return &ErrXactTgtInMaint{xaction, tname}
   772  }
   773  
   774  func (e *ErrXactTgtInMaint) Error() string {
   775  	return fmt.Sprintf("%s is in maintenance or being decommissioned - cannot run %s",
   776  		e.tname, e.xaction)
   777  }
   778  
   779  // ErrRangeNotSatisfiable
   780  // http.StatusRequestedRangeNotSatisfiable = 416 // RFC 9110, 15.5.17
   781  
   782  func NewErrRangeNotSatisfiable(err error, ranges []string, size int64) *ErrRangeNotSatisfiable {
   783  	return &ErrRangeNotSatisfiable{err, ranges, size}
   784  }
   785  
   786  func (e *ErrRangeNotSatisfiable) Error() string {
   787  	if e.err == nil {
   788  		s := "object size = " + strconv.FormatInt(e.size, 10)
   789  		return fmt.Sprintf("%s, range%s %v not satisfiable", s, cos.Plural(len(e.ranges)), e.ranges)
   790  	}
   791  	return e.err.Error()
   792  }
   793  
   794  func IsErrRangeNotSatisfiable(err error) bool {
   795  	_, ok := err.(*ErrRangeNotSatisfiable)
   796  	return ok
   797  }
   798  
   799  //
   800  // more is-error helpers
   801  //
   802  
   803  // nought: not a thing
   804  func IsErrBucketNought(err error) bool {
   805  	return IsErrBckNotFound(err) || IsErrRemoteBckNotFound(err) || isErrRemoteBucketOffline(err)
   806  }
   807  
   808  // lom.Load
   809  func IsErrObjNought(err error) bool {
   810  	return cos.IsNotExist(err, 0) || IsStatusNotFound(err) || isErrObjDefunct(err) || IsErrLmetaNotFound(err)
   811  }
   812  
   813  // used internally to report http.StatusNotFound _iff_ status is not set (is zero)
   814  func isErrNotFoundExtended(err error, status int) bool {
   815  	return IsErrBckNotFound(err) || IsErrRemoteBckNotFound(err) ||
   816  		IsErrMountpathNotFound(err) || IsErrXactNotFound(err) ||
   817  		cos.IsNotExist(err, status)
   818  }
   819  
   820  func IsFileAlreadyClosed(err error) bool {
   821  	return errors.Is(err, fs.ErrClosed)
   822  }
   823  
   824  func IsErrBucketLevel(err error) bool { return IsErrBucketNought(err) }
   825  func IsErrObjLevel(err error) bool    { return IsErrObjNought(err) }
   826  
   827  /////////////
   828  // ErrHTTP //
   829  /////////////
   830  
   831  func Str2HTTPErr(msg string) *ErrHTTP {
   832  	var herr ErrHTTP
   833  	if err := jsoniter.UnmarshalFromString(msg, &herr); err == nil {
   834  		return &herr
   835  	}
   836  	return nil
   837  }
   838  
   839  func Err2HTTPErr(err error) *ErrHTTP {
   840  	e, ok := err.(*ErrHTTP)
   841  	if !ok {
   842  		e = &ErrHTTP{}
   843  		if !errors.As(err, &e) {
   844  			return nil
   845  		}
   846  	}
   847  	return e
   848  }
   849  
   850  const maxTypeCodeLen = 30
   851  
   852  func TypeCodeHTTPErr(s string) (tcode string) {
   853  	if !strings.HasPrefix(s, "Err") {
   854  		return
   855  	}
   856  	for i := 3; i < min(maxTypeCodeLen, len(s)); i++ {
   857  		c := s[i]
   858  		if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') {
   859  			continue
   860  		}
   861  		if c == ':' && i+8 < len(s) && s[i+1] == ' ' {
   862  			tcode = s[:i]
   863  		}
   864  		break
   865  	}
   866  	return
   867  }
   868  
   869  func NewErrHTTP(r *http.Request, err error, ecode int) (e *ErrHTTP) {
   870  	e = &ErrHTTP{}
   871  	e.init(r, err, ecode)
   872  	return e
   873  }
   874  
   875  // uses `allocHterr` to allocate - caller must free via `FreeHterr`
   876  func InitErrHTTP(r *http.Request, err error, ecode int) (e *ErrHTTP) {
   877  	e = allocHterr()
   878  	e.init(r, err, ecode)
   879  	return e
   880  }
   881  
   882  func (e *ErrHTTP) init(r *http.Request, err error, ecode int) {
   883  	e.Status = http.StatusBadRequest
   884  	if ecode != 0 {
   885  		e.Status = ecode
   886  	}
   887  	tcode := fmt.Sprintf("%T", err)
   888  	if i := strings.Index(tcode, "."); i > 0 {
   889  		if pkg := tcode[:i]; pkg != "*errors" && pkg != "errors" {
   890  			e.TypeCode = tcode[i+1:]
   891  		}
   892  	}
   893  	_clean(err)
   894  	e.Message = err.Error()
   895  	if r != nil {
   896  		e.Method, e.URLPath = r.Method, r.URL.Path
   897  		e.RemoteAddr = r.RemoteAddr
   898  		e.Caller = r.Header.Get(apc.HdrCallerName)
   899  	}
   900  	e.Node = thisNodeName
   901  }
   902  
   903  func (e *ErrHTTP) Error() (s string) {
   904  	if e.TypeCode != "" && e.TypeCode != "ErrFailedTo" {
   905  		if !strings.Contains(e.Message, e.TypeCode+":") {
   906  			return e.TypeCode + ": " + e.Message
   907  		}
   908  	}
   909  	return e.Message
   910  }
   911  
   912  func _clean(err error) {
   913  	if cleanPathErr != nil {
   914  		cleanPathErr(err)
   915  	}
   916  }
   917  
   918  // Example:
   919  // ErrBckNotFound: bucket "ais://abc" does not exist: HEAD /v1/buckets/abc (p[kWQp8080]: htrun.go:1035 <- prxtrybck.go:180 <- ...
   920  func (e *ErrHTTP) StringEx() (s string) {
   921  	s = e.Error()
   922  	if e.Method != "" || e.URLPath != "" {
   923  		if !cos.IsLastB(s, '.') {
   924  			s += ":"
   925  		}
   926  		if e.Method != "" {
   927  			s += " " + e.Method
   928  		}
   929  		if e.URLPath != "" {
   930  			s += " " + e.URLPath
   931  		}
   932  	}
   933  	if thisNodeName != "" && !strings.Contains(e.Message, thisNodeName) {
   934  		s += " (failed at " + thisNodeName + ")"
   935  	}
   936  	if e.Caller != "" {
   937  		s += " (called by " + e.Caller + ")"
   938  	}
   939  	if len(e.trace) == 0 {
   940  		e._trace()
   941  	}
   942  	return s + " (" + string(e.trace) + ")"
   943  }
   944  
   945  func (e *ErrHTTP) _jsonError(buf *bytes.Buffer) {
   946  	enc := jsoniter.NewEncoder(buf)
   947  	enc.SetEscapeHTML(false) // stop from escaping `<`, `>` and `&`.
   948  	if err := enc.Encode(e); err != nil {
   949  		buf.Reset()
   950  		buf.WriteString(err.Error())
   951  	}
   952  }
   953  
   954  func (e *ErrHTTP) write(w http.ResponseWriter, r *http.Request, silent bool) {
   955  	if !silent {
   956  		s := e.StringEx()
   957  		if thisNodeName != "" && !strings.Contains(e.Message, thisNodeName) {
   958  			// node name instead of generic stack:
   959  			replaced1 := strings.Replace(s, stackTracePrefix, thisNodeName+": ", 1)
   960  			if replaced1 != s {
   961  				replaced2 := strings.Replace(replaced1, " (failed at "+thisNodeName+")", "", 1)
   962  				if replaced2 != replaced1 {
   963  					s = replaced2
   964  				}
   965  			}
   966  		}
   967  		nlog.Errorln(s)
   968  	}
   969  	hdr := w.Header()
   970  	hdr.Set(cos.HdrContentType, cos.ContentJSON)
   971  	hdr.Set(cos.HdrContentTypeOptions, "nosniff")
   972  
   973  	berr := NewBuffer()
   974  	e._jsonError(berr)
   975  	if r.Method == http.MethodHead {
   976  		hdr.Set(apc.HdrError, berr.String())
   977  		w.WriteHeader(e.Status)
   978  	} else {
   979  		w.WriteHeader(e.Status)
   980  		w.Write(berr.Bytes()) // no newline
   981  	}
   982  	FreeBuffer(berr)
   983  }
   984  
   985  func (e *ErrHTTP) _trace() {
   986  	buffer := bytes.NewBuffer(e.trace)
   987  	fmt.Fprint(buffer, stackTracePrefix)
   988  	for i := 1; i < 9; i++ {
   989  		_, file, line, ok := runtime.Caller(i)
   990  		if !ok {
   991  			break
   992  		}
   993  		if !strings.Contains(file, "aistore") {
   994  			break
   995  		}
   996  		f := filepath.Base(file)
   997  		if f == "err.go" {
   998  			continue
   999  		}
  1000  		if buffer.Len() > len(stackTracePrefix) {
  1001  			buffer.WriteString(" <- ")
  1002  		}
  1003  		fmt.Fprintf(buffer, "%s:%d", f, line)
  1004  	}
  1005  	fmt.Fprint(buffer, "]")
  1006  	e.trace = buffer.Bytes()
  1007  }
  1008  
  1009  func IsStatusServiceUnavailable(err error) (yes bool) {
  1010  	herr, ok := err.(*ErrHTTP)
  1011  	return ok && herr.Status == http.StatusServiceUnavailable
  1012  }
  1013  
  1014  func IsStatusNotFound(err error) (yes bool) {
  1015  	herr, ok := err.(*ErrHTTP)
  1016  	return ok && herr.Status == http.StatusNotFound
  1017  }
  1018  
  1019  func IsStatusBadGateway(err error) (yes bool) {
  1020  	herr, ok := err.(*ErrHTTP)
  1021  	return ok && herr.Status == http.StatusBadGateway
  1022  }
  1023  
  1024  func IsStatusGone(err error) (yes bool) {
  1025  	herr, ok := err.(*ErrHTTP)
  1026  	return ok && herr.Status == http.StatusGone
  1027  }
  1028  
  1029  //
  1030  // WriteErr and friends
  1031  //
  1032  
  1033  // sends HTTP response header with the provided status (alloc/free via mem-pool)
  1034  func WriteErr(w http.ResponseWriter, r *http.Request, err error, opts ...int /*[status[, silent]]*/) {
  1035  	if herr, allocated := err2HTTP(err); herr != nil {
  1036  		herr.Status = http.StatusBadRequest
  1037  		if len(opts) > 0 && opts[0] > http.StatusBadRequest {
  1038  			herr.Status = opts[0]
  1039  		}
  1040  		herr.write(w, r, len(opts) > 1 /*silent*/)
  1041  		if allocated {
  1042  			FreeHterr(herr)
  1043  		}
  1044  		return
  1045  	}
  1046  	var (
  1047  		herr   = allocHterr()
  1048  		l      = len(opts)
  1049  		status = http.StatusBadRequest
  1050  	)
  1051  
  1052  	// assign status (in order of priority)
  1053  	if cos.IsErrNotFound(err) {
  1054  		// NOTE: override opts[0] status, e.g.: "remote cluster "uuid" does not exist, status=500"
  1055  		status = http.StatusNotFound
  1056  	} else if l > 0 {
  1057  		status = opts[0]
  1058  	} else if errf, ok := err.(*ErrFailedTo); ok {
  1059  		status = errf.status
  1060  	} else {
  1061  		switch {
  1062  		case isErrNotFoundExtended(err, status):
  1063  			status = http.StatusNotFound
  1064  		case IsErrCapExceeded(err):
  1065  			status = http.StatusInsufficientStorage
  1066  		case IsErrRangeNotSatisfiable(err):
  1067  			status = http.StatusRequestedRangeNotSatisfiable
  1068  		case isErrUnsupp(err), isErrNotImpl(err):
  1069  			status = http.StatusNotImplemented
  1070  		}
  1071  	}
  1072  
  1073  	herr.init(r, err, status)
  1074  	herr.write(w, r, l > 1)
  1075  	FreeHterr(herr)
  1076  }
  1077  
  1078  // NOTE: internal use w/ duplication/simplicity traded off
  1079  func err2HTTP(err error) (*ErrHTTP, bool) {
  1080  	if e, ok := err.(*ErrHTTP); ok {
  1081  		return e, false
  1082  	}
  1083  	e := allocHterr()
  1084  	if !errors.As(err, &e) {
  1085  		FreeHterr(e)
  1086  		return nil, false
  1087  	}
  1088  	return e, true
  1089  }
  1090  
  1091  // Create ErrHTTP (based on `msg` and `opts`) and write it into HTTP response.
  1092  func WriteErrMsg(w http.ResponseWriter, r *http.Request, msg string, opts ...int) {
  1093  	var ecode int
  1094  	if len(opts) > 0 {
  1095  		ecode = opts[0]
  1096  	}
  1097  	herr := InitErrHTTP(r, errors.New(msg), ecode)
  1098  	herr.write(w, r, len(opts) > 1 /*silent*/)
  1099  	FreeHterr(herr)
  1100  }
  1101  
  1102  // 405 Method Not Allowed, see:
  1103  // * https://www.rfc-editor.org/rfc/rfc7231#section-6.5.5
  1104  func WriteErr405(w http.ResponseWriter, r *http.Request, methods ...string) {
  1105  	w.Header().Set("Allow", strings.Join(methods, ", "))
  1106  	if r.Method == http.MethodOptions {
  1107  		w.WriteHeader(http.StatusOK)
  1108  	} else {
  1109  		http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
  1110  	}
  1111  }
  1112  
  1113  //
  1114  // 1) ErrHTTP struct pool
  1115  // 2) bytes.Buffer pool
  1116  //
  1117  
  1118  const maxBuffer = 4 * cos.KiB
  1119  
  1120  var (
  1121  	errPool sync.Pool
  1122  	bufPool sync.Pool
  1123  
  1124  	err0 ErrHTTP
  1125  )
  1126  
  1127  func allocHterr() (a *ErrHTTP) {
  1128  	if v := errPool.Get(); v != nil {
  1129  		a = v.(*ErrHTTP)
  1130  		return
  1131  	}
  1132  	return &ErrHTTP{}
  1133  }
  1134  
  1135  func FreeHterr(a *ErrHTTP) {
  1136  	trace := a.trace
  1137  	*a = err0
  1138  	if trace != nil {
  1139  		a.trace = trace[:0]
  1140  	}
  1141  	errPool.Put(a)
  1142  }
  1143  
  1144  func NewBuffer() (buf *bytes.Buffer) {
  1145  	if v := bufPool.Get(); v != nil {
  1146  		buf = v.(*bytes.Buffer)
  1147  	} else {
  1148  		buf = bytes.NewBuffer(nil)
  1149  	}
  1150  	return
  1151  }
  1152  
  1153  func FreeBuffer(buf *bytes.Buffer) {
  1154  	if buf.Cap() > maxBuffer {
  1155  		return
  1156  	}
  1157  	buf.Reset()
  1158  	bufPool.Put(buf)
  1159  }