github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmn/config.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  	"crypto/tls"
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"net/url"
    14  	"os"
    15  	"path"
    16  	"path/filepath"
    17  	"strconv"
    18  	"strings"
    19  	"time"
    20  
    21  	"github.com/NVIDIA/aistore/api/apc"
    22  	"github.com/NVIDIA/aistore/cmn/cos"
    23  	"github.com/NVIDIA/aistore/cmn/debug"
    24  	"github.com/NVIDIA/aistore/cmn/feat"
    25  	"github.com/NVIDIA/aistore/cmn/fname"
    26  	"github.com/NVIDIA/aistore/cmn/jsp"
    27  	"github.com/NVIDIA/aistore/cmn/nlog"
    28  	jsoniter "github.com/json-iterator/go"
    29  )
    30  
    31  type (
    32  	Validator interface {
    33  		Validate() error
    34  	}
    35  	PropsValidator interface {
    36  		ValidateAsProps(arg ...any) error
    37  	}
    38  )
    39  
    40  // Config is a single control structure (persistent and versioned)
    41  // that contains both cluster (global) and node (local) configuration
    42  // Naming convention for setting/getting values: (parent section json tag . child json tag)
    43  // See also: `IterFields`, `IterFieldNameSepa`
    44  type (
    45  	Config struct {
    46  		role          string `list:"omit"` // Proxy or Target
    47  		ClusterConfig `json:",inline"`
    48  		LocalConfig   `json:",inline"`
    49  	}
    50  )
    51  
    52  // local config
    53  type (
    54  	LocalConfig struct {
    55  		ConfigDir string         `json:"confdir"`
    56  		LogDir    string         `json:"log_dir"`
    57  		HostNet   LocalNetConfig `json:"host_net"`
    58  		FSP       FSPConf        `json:"fspaths"`
    59  		TestFSP   TestFSPConf    `json:"test_fspaths"`
    60  	}
    61  
    62  	// ais node: (local) network config
    63  	LocalNetConfig struct {
    64  		Hostname             string `json:"hostname"`
    65  		HostnameIntraControl string `json:"hostname_intra_control"`
    66  		HostnameIntraData    string `json:"hostname_intra_data"`
    67  		Port                 int    `json:"port,string"`               // listening port
    68  		PortIntraControl     int    `json:"port_intra_control,string"` // --/-- for intra-cluster control
    69  		PortIntraData        int    `json:"port_intra_data,string"`    // --/-- for intra-cluster data
    70  		// omit
    71  		UseIntraControl bool `json:"-"`
    72  		UseIntraData    bool `json:"-"`
    73  	}
    74  
    75  	// ais node: fspaths (a.k.a. mountpaths) and their respective (optional) labels
    76  	FSPConf struct {
    77  		Paths cos.StrKVs `json:"paths,omitempty" list:"readonly"`
    78  	}
    79  	// [backward compatibility]: v3.22 and prior
    80  	FSPConfV322 struct {
    81  		Paths cos.StrSet `json:"paths,omitempty" list:"readonly"`
    82  	}
    83  
    84  	TestFSPConf struct {
    85  		Root     string `json:"root"`
    86  		Count    int    `json:"count"`
    87  		Instance int    `json:"instance"`
    88  	}
    89  )
    90  
    91  // global configuration
    92  type (
    93  	ClusterConfig struct {
    94  		Ext        any            `json:"ext,omitempty"` // within meta-version extensions
    95  		Backend    BackendConf    `json:"backend" allow:"cluster"`
    96  		Mirror     MirrorConf     `json:"mirror" allow:"cluster"`
    97  		EC         ECConf         `json:"ec" allow:"cluster"`
    98  		Log        LogConf        `json:"log"`
    99  		Periodic   PeriodConf     `json:"periodic"`
   100  		Timeout    TimeoutConf    `json:"timeout"`
   101  		Client     ClientConf     `json:"client"`
   102  		Proxy      ProxyConf      `json:"proxy" allow:"cluster"`
   103  		Space      SpaceConf      `json:"space"`
   104  		LRU        LRUConf        `json:"lru"`
   105  		Disk       DiskConf       `json:"disk"`
   106  		Rebalance  RebalanceConf  `json:"rebalance" allow:"cluster"`
   107  		Resilver   ResilverConf   `json:"resilver"`
   108  		Cksum      CksumConf      `json:"checksum"`
   109  		Versioning VersionConf    `json:"versioning" allow:"cluster"`
   110  		Net        NetConf        `json:"net"`
   111  		FSHC       FSHCConf       `json:"fshc"`
   112  		Auth       AuthConf       `json:"auth"`
   113  		Keepalive  KeepaliveConf  `json:"keepalivetracker"`
   114  		Downloader DownloaderConf `json:"downloader"`
   115  		Dsort      DsortConf      `json:"distributed_sort"`
   116  		Transport  TransportConf  `json:"transport"`
   117  		Memsys     MemsysConf     `json:"memsys"`
   118  
   119  		// Transform (offline) or Copy src Bucket => dst bucket
   120  		TCB TCBConf `json:"tcb"`
   121  
   122  		// metadata write policy: (immediate | delayed | never)
   123  		WritePolicy WritePolicyConf `json:"write_policy"`
   124  
   125  		// standalone enumerated features that can be configured
   126  		// to flip assorted global defaults (see cmn/feat/feat.go)
   127  		Features feat.Flags `json:"features,string" allow:"cluster"`
   128  
   129  		// read-only
   130  		LastUpdated string `json:"lastupdate_time"`       // timestamp
   131  		UUID        string `json:"uuid"`                  // UUID
   132  		Version     int64  `json:"config_version,string"` // version
   133  	}
   134  	ConfigToSet struct {
   135  		// ClusterConfig
   136  		Backend     *BackendConf          `json:"backend,omitempty"`
   137  		Mirror      *MirrorConfToSet      `json:"mirror,omitempty"`
   138  		EC          *ECConfToSet          `json:"ec,omitempty"`
   139  		Log         *LogConfToSet         `json:"log,omitempty"`
   140  		Periodic    *PeriodConfToSet      `json:"periodic,omitempty"`
   141  		Timeout     *TimeoutConfToSet     `json:"timeout,omitempty"`
   142  		Client      *ClientConfToSet      `json:"client,omitempty"`
   143  		Space       *SpaceConfToSet       `json:"space,omitempty"`
   144  		LRU         *LRUConfToSet         `json:"lru,omitempty"`
   145  		Disk        *DiskConfToSet        `json:"disk,omitempty"`
   146  		Rebalance   *RebalanceConfToSet   `json:"rebalance,omitempty"`
   147  		Resilver    *ResilverConfToSet    `json:"resilver,omitempty"`
   148  		Cksum       *CksumConfToSet       `json:"checksum,omitempty"`
   149  		Versioning  *VersionConfToSet     `json:"versioning,omitempty"`
   150  		Net         *NetConfToSet         `json:"net,omitempty"`
   151  		FSHC        *FSHCConfToSet        `json:"fshc,omitempty"`
   152  		Auth        *AuthConfToSet        `json:"auth,omitempty"`
   153  		Keepalive   *KeepaliveConfToSet   `json:"keepalivetracker,omitempty"`
   154  		Downloader  *DownloaderConfToSet  `json:"downloader,omitempty"`
   155  		Dsort       *DsortConfToSet       `json:"distributed_sort,omitempty"`
   156  		Transport   *TransportConfToSet   `json:"transport,omitempty"`
   157  		Memsys      *MemsysConfToSet      `json:"memsys,omitempty"`
   158  		TCB         *TCBConfToSet         `json:"tcb,omitempty"`
   159  		WritePolicy *WritePolicyConfToSet `json:"write_policy,omitempty"`
   160  		Proxy       *ProxyConfToSet       `json:"proxy,omitempty"`
   161  		Features    *feat.Flags           `json:"features,string,omitempty"`
   162  
   163  		// LocalConfig
   164  		FSP *FSPConf `json:"fspaths,omitempty"`
   165  	}
   166  
   167  	BackendConf struct {
   168  		// provider implementation-dependent
   169  		Conf map[string]any `json:"conf,omitempty"`
   170  		// 3rd party Cloud(s) -- set during validation
   171  		Providers map[string]Ns `json:"-"`
   172  	}
   173  	BackendConfAIS map[string][]string // cluster alias -> [urls...]
   174  
   175  	MirrorConf struct {
   176  		Copies  int64 `json:"copies"`       // num copies
   177  		Burst   int   `json:"burst_buffer"` // xaction channel (buffer) size
   178  		Enabled bool  `json:"enabled"`      // enabled (to generate copies)
   179  	}
   180  	MirrorConfToSet struct {
   181  		Copies  *int64 `json:"copies,omitempty"`
   182  		Burst   *int   `json:"burst_buffer,omitempty"`
   183  		Enabled *bool  `json:"enabled,omitempty"`
   184  	}
   185  
   186  	ECConf struct {
   187  		Compression string `json:"compression"` // enum { CompressAlways, ... } in api/apc/compression.go
   188  
   189  		// ObjSizeLimit is object size threshold _separating_ intra-cluster mirroring from
   190  		// erasure coding.
   191  		//
   192  		// The value 0 (zero) indicates that objects of any size
   193  		// are to be sliced, to produce (D) data slices and (P) erasure coded parity slices.
   194  		// On the other hand, the value -1 specifies that absolutely all objects of any size
   195  		// must be replicated as is. In effect, the (-1) option provides data protection via
   196  		// intra-cluster (P+1)-way replication (a.k.a. mirroring).
   197  		//
   198  		// In all cases, a given (D, P) configuration provides node-level redundancy,
   199  		// whereby P nodes can be lost without incurring loss of data.
   200  		ObjSizeLimit int64 `json:"objsize_limit"`
   201  
   202  		// Number of data (D) slices; the value 1 will have an effect of producing
   203  		// (P) additional full-size replicas.
   204  		DataSlices int `json:"data_slices"`
   205  
   206  		// Depending on the object size and `ObjSizeLimit`, the value of `ParitySlices` (or P) indicates:
   207  		// - a number of additional parity slices (generated or _computed_ from the (D) data slices),
   208  		// or:
   209  		// - a number of full object replicas (copies).
   210  		// In all cases, the same rule applies: all slices and/or all full copies are stored on different
   211  		// storage nodes (a.k.a. targets).
   212  		ParitySlices int `json:"parity_slices"`
   213  
   214  		SbundleMult int `json:"bundle_multiplier"` // stream-bundle multiplier: num streams to destination
   215  
   216  		Enabled  bool `json:"enabled"`   // EC is enabled
   217  		DiskOnly bool `json:"disk_only"` // if true, EC does not use SGL - data goes directly to drives
   218  	}
   219  	ECConfToSet struct {
   220  		ObjSizeLimit *int64  `json:"objsize_limit,omitempty"`
   221  		Compression  *string `json:"compression,omitempty"`
   222  		SbundleMult  *int    `json:"bundle_multiplier,omitempty"`
   223  		DataSlices   *int    `json:"data_slices,omitempty"`
   224  		ParitySlices *int    `json:"parity_slices,omitempty"`
   225  		Enabled      *bool   `json:"enabled,omitempty"`
   226  		DiskOnly     *bool   `json:"disk_only,omitempty"`
   227  	}
   228  
   229  	LogConf struct {
   230  		Level     cos.LogLevel `json:"level"`      // log level (aka verbosity)
   231  		MaxSize   cos.SizeIEC  `json:"max_size"`   // exceeding this size triggers log rotation
   232  		MaxTotal  cos.SizeIEC  `json:"max_total"`  // (sum individual log sizes); exceeding this number triggers cleanup
   233  		FlushTime cos.Duration `json:"flush_time"` // log flush interval
   234  		StatsTime cos.Duration `json:"stats_time"` // (not used)
   235  		ToStderr  bool         `json:"to_stderr"`  // Log only to stderr instead of files.
   236  	}
   237  	LogConfToSet struct {
   238  		Level     *cos.LogLevel `json:"level,omitempty"`
   239  		ToStderr  *bool         `json:"to_stderr,omitempty"`
   240  		MaxSize   *cos.SizeIEC  `json:"max_size,omitempty"`
   241  		MaxTotal  *cos.SizeIEC  `json:"max_total,omitempty"`
   242  		FlushTime *cos.Duration `json:"flush_time,omitempty"`
   243  		StatsTime *cos.Duration `json:"stats_time,omitempty"`
   244  	}
   245  
   246  	// NOTE: StatsTime is a one important timer
   247  	PeriodConf struct {
   248  		StatsTime     cos.Duration `json:"stats_time"`      // collect and publish stats; other house-keeping
   249  		RetrySyncTime cos.Duration `json:"retry_sync_time"` // metasync retry
   250  		NotifTime     cos.Duration `json:"notif_time"`      // (IC notifications)
   251  	}
   252  	PeriodConfToSet struct {
   253  		StatsTime     *cos.Duration `json:"stats_time,omitempty"`
   254  		RetrySyncTime *cos.Duration `json:"retry_sync_time,omitempty"`
   255  		NotifTime     *cos.Duration `json:"notif_time,omitempty"`
   256  	}
   257  
   258  	// maximum intra-cluster latencies (in the increasing order)
   259  	TimeoutConf struct {
   260  		CplaneOperation cos.Duration `json:"cplane_operation"` // read-mostly via global cmn.Rom.CplaneOperation
   261  		MaxKeepalive    cos.Duration `json:"max_keepalive"`    // ditto, cmn.Rom.MaxKeepalive - see below
   262  		MaxHostBusy     cos.Duration `json:"max_host_busy"`
   263  		Startup         cos.Duration `json:"startup_time"`
   264  		JoinAtStartup   cos.Duration `json:"join_startup_time"` // (join cluster at startup) timeout
   265  		SendFile        cos.Duration `json:"send_file_time"`
   266  	}
   267  	TimeoutConfToSet struct {
   268  		CplaneOperation *cos.Duration `json:"cplane_operation,omitempty"`
   269  		MaxKeepalive    *cos.Duration `json:"max_keepalive,omitempty"`
   270  		MaxHostBusy     *cos.Duration `json:"max_host_busy,omitempty"`
   271  		Startup         *cos.Duration `json:"startup_time,omitempty"`
   272  		JoinAtStartup   *cos.Duration `json:"join_startup_time,omitempty"`
   273  		SendFile        *cos.Duration `json:"send_file_time,omitempty"`
   274  	}
   275  
   276  	ClientConf struct {
   277  		Timeout        cos.Duration `json:"client_timeout"`
   278  		TimeoutLong    cos.Duration `json:"client_long_timeout"`
   279  		ListObjTimeout cos.Duration `json:"list_timeout"`
   280  	}
   281  	ClientConfToSet struct {
   282  		Timeout        *cos.Duration `json:"client_timeout,omitempty"` // readonly as far as intra-cluster
   283  		TimeoutLong    *cos.Duration `json:"client_long_timeout,omitempty"`
   284  		ListObjTimeout *cos.Duration `json:"list_timeout,omitempty"`
   285  	}
   286  
   287  	ProxyConf struct {
   288  		PrimaryURL   string `json:"primary_url"`
   289  		OriginalURL  string `json:"original_url"`
   290  		DiscoveryURL string `json:"discovery_url"`
   291  		NonElectable bool   `json:"non_electable"`
   292  	}
   293  	ProxyConfToSet struct {
   294  		PrimaryURL   *string `json:"primary_url,omitempty"`
   295  		OriginalURL  *string `json:"original_url,omitempty"`
   296  		DiscoveryURL *string `json:"discovery_url,omitempty"`
   297  		NonElectable *bool   `json:"non_electable,omitempty"`
   298  	}
   299  
   300  	SpaceConf struct {
   301  		// Storage Cleanup watermark: used capacity (%) that triggers cleanup
   302  		// (deleted objects and buckets, extra copies, etc.)
   303  		CleanupWM int64 `json:"cleanupwm"`
   304  
   305  		// LowWM: used capacity low-watermark (% of total local storage capacity)
   306  		LowWM int64 `json:"lowwm"`
   307  
   308  		// HighWM: used capacity high-watermark (% of total local storage capacity)
   309  		// - LRU starts evicting objects when the currently used capacity (used-cap) gets above HighWM
   310  		// - and keeps evicting objects until the used-cap gets below LowWM
   311  		// - while self-throttling itself in accordance with target utilization
   312  		HighWM int64 `json:"highwm"`
   313  
   314  		// Out-of-Space: if exceeded, the target starts failing new PUTs and keeps
   315  		// failing them until its local used-cap gets back below HighWM (see above)
   316  		OOS int64 `json:"out_of_space"`
   317  	}
   318  	SpaceConfToSet struct {
   319  		CleanupWM *int64 `json:"cleanupwm,omitempty"`
   320  		LowWM     *int64 `json:"lowwm,omitempty"`
   321  		HighWM    *int64 `json:"highwm,omitempty"`
   322  		OOS       *int64 `json:"out_of_space,omitempty"`
   323  	}
   324  
   325  	LRUConf struct {
   326  		// DontEvictTimeStr denotes the period of time during which eviction of an object
   327  		// is forbidden [atime, atime + DontEvictTime]
   328  		DontEvictTime cos.Duration `json:"dont_evict_time"`
   329  
   330  		// CapacityUpdTimeStr denotes the frequency at which AIStore updates local capacity utilization
   331  		CapacityUpdTime cos.Duration `json:"capacity_upd_time"`
   332  
   333  		// Enabled: LRU will only run when set to true
   334  		Enabled bool `json:"enabled"`
   335  	}
   336  	LRUConfToSet struct {
   337  		DontEvictTime   *cos.Duration `json:"dont_evict_time,omitempty"`
   338  		CapacityUpdTime *cos.Duration `json:"capacity_upd_time,omitempty"`
   339  		Enabled         *bool         `json:"enabled,omitempty"`
   340  	}
   341  
   342  	DiskConf struct {
   343  		DiskUtilLowWM   int64        `json:"disk_util_low_wm"`  // no throttling below
   344  		DiskUtilHighWM  int64        `json:"disk_util_high_wm"` // throttle longer when above
   345  		DiskUtilMaxWM   int64        `json:"disk_util_max_wm"`
   346  		IostatTimeLong  cos.Duration `json:"iostat_time_long"`
   347  		IostatTimeShort cos.Duration `json:"iostat_time_short"`
   348  	}
   349  	DiskConfToSet struct {
   350  		DiskUtilLowWM   *int64        `json:"disk_util_low_wm,omitempty"`
   351  		DiskUtilHighWM  *int64        `json:"disk_util_high_wm,omitempty"`
   352  		DiskUtilMaxWM   *int64        `json:"disk_util_max_wm,omitempty"`
   353  		IostatTimeLong  *cos.Duration `json:"iostat_time_long,omitempty"`
   354  		IostatTimeShort *cos.Duration `json:"iostat_time_short,omitempty"`
   355  	}
   356  
   357  	RebalanceConf struct {
   358  		Compression   string       `json:"compression"`       // enum { CompressAlways, ... } in api/apc/compression.go
   359  		DestRetryTime cos.Duration `json:"dest_retry_time"`   // max wait for ACKs & neighbors to complete
   360  		SbundleMult   int          `json:"bundle_multiplier"` // stream-bundle multiplier: num streams to destination
   361  		Enabled       bool         `json:"enabled"`           // true=auto-rebalance | manual rebalancing
   362  	}
   363  	RebalanceConfToSet struct {
   364  		DestRetryTime *cos.Duration `json:"dest_retry_time,omitempty"`
   365  		Compression   *string       `json:"compression,omitempty"`
   366  		SbundleMult   *int          `json:"bundle_multiplier"`
   367  		Enabled       *bool         `json:"enabled,omitempty"`
   368  	}
   369  
   370  	ResilverConf struct {
   371  		Enabled bool `json:"enabled"` // true=auto-resilver | manual resilvering
   372  	}
   373  	ResilverConfToSet struct {
   374  		Enabled *bool `json:"enabled,omitempty"`
   375  	}
   376  
   377  	CksumConf struct {
   378  		// (note that `ChecksumNone` ("none") disables checksumming)
   379  		Type string `json:"type"`
   380  
   381  		// validate the checksum of the object that we cold-GET
   382  		// or download from remote location (e.g., cloud bucket)
   383  		ValidateColdGet bool `json:"validate_cold_get"`
   384  
   385  		// validate object's version (if exists and provided) and its checksum -
   386  		// if either value fail to match, the object is removed from ais.
   387  		//
   388  		// NOTE: object versioning is backend-specific and is may _not_ be supported by a given
   389  		// (supported) backends - see docs for details.
   390  		ValidateWarmGet bool `json:"validate_warm_get"`
   391  
   392  		// determines whether to validate checksums of objects
   393  		// migrated or replicated within the cluster
   394  		ValidateObjMove bool `json:"validate_obj_move"`
   395  
   396  		// EnableReadRange: Return read range checksum otherwise return entire object checksum.
   397  		EnableReadRange bool `json:"enable_read_range"`
   398  	}
   399  	CksumConfToSet struct {
   400  		Type            *string `json:"type,omitempty"`
   401  		ValidateColdGet *bool   `json:"validate_cold_get,omitempty"`
   402  		ValidateWarmGet *bool   `json:"validate_warm_get,omitempty"`
   403  		ValidateObjMove *bool   `json:"validate_obj_move,omitempty"`
   404  		EnableReadRange *bool   `json:"enable_read_range,omitempty"`
   405  	}
   406  
   407  	VersionConf struct {
   408  		// Determines if versioning is enabled
   409  		Enabled bool `json:"enabled"`
   410  
   411  		// Validate remote version and, possibly, update in-cluster ("cached") copy.
   412  		// Scenarios include (but are not limited to):
   413  		// - warm GET
   414  		// - prefetch bucket (*)
   415  		// - prefetch multiple objects (see api/multiobj.go)
   416  		// - copy bucket
   417  		// Applies to Cloud and remote AIS buckets - generally, buckets that have remote backends
   418  		// that in turn provide some form of object versioning.
   419  		// (*) Xactions (ie., jobs) that read-access multiple objects (e.g., prefetch, copy-bucket)
   420  		// may support operation-scope option to synchronize remote content (to aistore) - the option
   421  		// not requiring changing bucket configuration.
   422  		// See also:
   423  		// - apc.QparamLatestVer, apc.PrefetchMsg, apc.CopyBckMsg
   424  		ValidateWarmGet bool `json:"validate_warm_get"`
   425  
   426  		// A stronger variant of the above that in addition entails:
   427  		// - deleting in-cluster object if its remote ("cached") counterpart does not exist
   428  		// See also: apc.QparamSync, apc.CopyBckMsg
   429  		Sync bool `json:"synchronize"`
   430  	}
   431  	VersionConfToSet struct {
   432  		Enabled         *bool `json:"enabled,omitempty"`
   433  		ValidateWarmGet *bool `json:"validate_warm_get,omitempty"`
   434  		Sync            *bool `json:"synchronize,omitempty"`
   435  	}
   436  
   437  	NetConf struct {
   438  		L4   L4Conf   `json:"l4"`
   439  		HTTP HTTPConf `json:"http"`
   440  	}
   441  	NetConfToSet struct {
   442  		HTTP *HTTPConfToSet `json:"http,omitempty"`
   443  	}
   444  
   445  	L4Conf struct {
   446  		Proto         string `json:"proto"`           // tcp, udp
   447  		SndRcvBufSize int    `json:"sndrcv_buf_size"` // SO_RCVBUF and SO_SNDBUF
   448  	}
   449  
   450  	HTTPConf struct {
   451  		Proto           string `json:"-"`                 // http or https (set depending on `UseHTTPS`)
   452  		Certificate     string `json:"server_crt"`        // HTTPS: X509 certificate
   453  		CertKey         string `json:"server_key"`        // HTTPS: X509 key
   454  		ServerNameTLS   string `json:"domain_tls"`        // #6410
   455  		ClientCA        string `json:"client_ca_tls"`     // #6410
   456  		ClientAuthTLS   int    `json:"client_auth_tls"`   // #6410 tls.ClientAuthType enum
   457  		WriteBufferSize int    `json:"write_buffer_size"` // http.Transport.WriteBufferSize; zero defaults to 4KB
   458  		ReadBufferSize  int    `json:"read_buffer_size"`  // http.Transport.ReadBufferSize; ditto
   459  		UseHTTPS        bool   `json:"use_https"`         // use HTTPS
   460  		SkipVerifyCrt   bool   `json:"skip_verify"`       // skip X509 cert verification (used with self-signed certs)
   461  		Chunked         bool   `json:"chunked_transfer"`  // (https://tools.ietf.org/html/rfc7230#page-36; not used since 02/23)
   462  	}
   463  	HTTPConfToSet struct {
   464  		Certificate     *string `json:"server_crt,omitempty"`
   465  		CertKey         *string `json:"server_key,omitempty"`
   466  		ServerNameTLS   *string `json:"domain_tls,omitempty"`
   467  		ClientCA        *string `json:"client_ca_tls,omitempty"`
   468  		WriteBufferSize *int    `json:"write_buffer_size,omitempty" list:"readonly"`
   469  		ReadBufferSize  *int    `json:"read_buffer_size,omitempty" list:"readonly"`
   470  		ClientAuthTLS   *int    `json:"client_auth_tls,omitempty"`
   471  		UseHTTPS        *bool   `json:"use_https,omitempty"`
   472  		SkipVerifyCrt   *bool   `json:"skip_verify,omitempty"`
   473  		Chunked         *bool   `json:"chunked_transfer,omitempty"`
   474  	}
   475  
   476  	FSHCConf struct {
   477  		TestFileCount int  `json:"test_files"`  // number of files to read/write
   478  		ErrorLimit    int  `json:"error_limit"` // exceeding err limit causes disabling mountpath
   479  		Enabled       bool `json:"enabled"`
   480  	}
   481  	FSHCConfToSet struct {
   482  		TestFileCount *int  `json:"test_files,omitempty"`
   483  		ErrorLimit    *int  `json:"error_limit,omitempty"`
   484  		Enabled       *bool `json:"enabled,omitempty"`
   485  	}
   486  
   487  	AuthConf struct {
   488  		Secret  string `json:"secret"`
   489  		Enabled bool   `json:"enabled"`
   490  	}
   491  	AuthConfToSet struct {
   492  		Secret  *string `json:"secret,omitempty"`
   493  		Enabled *bool   `json:"enabled,omitempty"`
   494  	}
   495  
   496  	// keepalive tracker
   497  	KeepaliveTrackerConf struct {
   498  		Name     string       `json:"name"`     // "heartbeat" (other enumerated values TBD)
   499  		Interval cos.Duration `json:"interval"` // keepalive interval
   500  		Factor   uint8        `json:"factor"`   // only average
   501  	}
   502  	KeepaliveTrackerConfToSet struct {
   503  		Interval *cos.Duration `json:"interval,omitempty"`
   504  		Name     *string       `json:"name,omitempty" list:"readonly"`
   505  		Factor   *uint8        `json:"factor,omitempty"`
   506  	}
   507  
   508  	KeepaliveConf struct {
   509  		Proxy       KeepaliveTrackerConf `json:"proxy"`  // how proxy tracks target keepalives
   510  		Target      KeepaliveTrackerConf `json:"target"` // how target tracks primary proxies keepalives
   511  		RetryFactor uint8                `json:"retry_factor"`
   512  	}
   513  	KeepaliveConfToSet struct {
   514  		Proxy       *KeepaliveTrackerConfToSet `json:"proxy,omitempty"`
   515  		Target      *KeepaliveTrackerConfToSet `json:"target,omitempty"`
   516  		RetryFactor *uint8                     `json:"retry_factor,omitempty"`
   517  	}
   518  
   519  	DownloaderConf struct {
   520  		Timeout cos.Duration `json:"timeout"`
   521  	}
   522  	DownloaderConfToSet struct {
   523  		Timeout *cos.Duration `json:"timeout,omitempty"`
   524  	}
   525  
   526  	DsortConf struct {
   527  		DuplicatedRecords   string       `json:"duplicated_records"`
   528  		MissingShards       string       `json:"missing_shards"` // cmn.SupportedReactions enum
   529  		EKMMalformedLine    string       `json:"ekm_malformed_line"`
   530  		EKMMissingKey       string       `json:"ekm_missing_key"`
   531  		DefaultMaxMemUsage  string       `json:"default_max_mem_usage"`
   532  		CallTimeout         cos.Duration `json:"call_timeout"`
   533  		DsorterMemThreshold string       `json:"dsorter_mem_threshold"`
   534  		Compression         string       `json:"compression"`       // {CompressAlways,...} in api/apc/compression.go
   535  		SbundleMult         int          `json:"bundle_multiplier"` // stream-bundle multiplier: num to destination
   536  	}
   537  	DsortConfToSet struct {
   538  		DuplicatedRecords   *string       `json:"duplicated_records,omitempty"`
   539  		MissingShards       *string       `json:"missing_shards,omitempty"`
   540  		EKMMalformedLine    *string       `json:"ekm_malformed_line,omitempty"`
   541  		EKMMissingKey       *string       `json:"ekm_missing_key,omitempty"`
   542  		DefaultMaxMemUsage  *string       `json:"default_max_mem_usage,omitempty"`
   543  		CallTimeout         *cos.Duration `json:"call_timeout,omitempty"`
   544  		DsorterMemThreshold *string       `json:"dsorter_mem_threshold,omitempty"`
   545  		Compression         *string       `json:"compression,omitempty"`
   546  		SbundleMult         *int          `json:"bundle_multiplier,omitempty"`
   547  	}
   548  
   549  	TransportConf struct {
   550  		MaxHeaderSize int `json:"max_header"`   // max transport header buffer (default=4K)
   551  		Burst         int `json:"burst_buffer"` // num sends with no back pressure; see also AIS_STREAM_BURST_NUM
   552  		// two no-new-transmissions durations:
   553  		// * IdleTeardown: sender terminates the connection (to reestablish it upon the very first/next PDU)
   554  		// * QuiesceTime:  safe to terminate or transition to the next (in re: rebalance) stage
   555  		IdleTeardown cos.Duration `json:"idle_teardown"`
   556  		QuiesceTime  cos.Duration `json:"quiescent"`
   557  		// lz4
   558  		// max uncompressed block size, one of [64K, 256K(*), 1M, 4M]
   559  		// fastcompression.blogspot.com/2013/04/lz4-streaming-format-final.html
   560  		LZ4BlockMaxSize  cos.SizeIEC `json:"lz4_block"`
   561  		LZ4FrameChecksum bool        `json:"lz4_frame_checksum"`
   562  	}
   563  	TransportConfToSet struct {
   564  		MaxHeaderSize    *int          `json:"max_header,omitempty" list:"readonly"`
   565  		Burst            *int          `json:"burst_buffer,omitempty" list:"readonly"`
   566  		IdleTeardown     *cos.Duration `json:"idle_teardown,omitempty"`
   567  		QuiesceTime      *cos.Duration `json:"quiescent,omitempty"`
   568  		LZ4BlockMaxSize  *cos.SizeIEC  `json:"lz4_block,omitempty"`
   569  		LZ4FrameChecksum *bool         `json:"lz4_frame_checksum,omitempty"`
   570  	}
   571  
   572  	MemsysConf struct {
   573  		MinFree        cos.SizeIEC  `json:"min_free"`
   574  		DefaultBufSize cos.SizeIEC  `json:"default_buf"`
   575  		SizeToGC       cos.SizeIEC  `json:"to_gc"`
   576  		HousekeepTime  cos.Duration `json:"hk_time"`
   577  		MinPctTotal    int          `json:"min_pct_total"`
   578  		MinPctFree     int          `json:"min_pct_free"`
   579  	}
   580  	MemsysConfToSet struct {
   581  		MinFree        *cos.SizeIEC  `json:"min_free,omitempty"`
   582  		DefaultBufSize *cos.SizeIEC  `json:"default_buf,omitempty"`
   583  		SizeToGC       *cos.SizeIEC  `json:"to_gc,omitempty"`
   584  		HousekeepTime  *cos.Duration `json:"hk_time,omitempty"`
   585  		MinPctTotal    *int          `json:"min_pct_total,omitempty"`
   586  		MinPctFree     *int          `json:"min_pct_free,omitempty"`
   587  	}
   588  
   589  	TCBConf struct {
   590  		Compression string `json:"compression"`       // enum { CompressAlways, ... } in api/apc/compression.go
   591  		SbundleMult int    `json:"bundle_multiplier"` // stream-bundle multiplier: num streams to destination
   592  	}
   593  	TCBConfToSet struct {
   594  		Compression *string `json:"compression,omitempty"`
   595  		SbundleMult *int    `json:"bundle_multiplier,omitempty"`
   596  	}
   597  
   598  	WritePolicyConf struct {
   599  		Data apc.WritePolicy `json:"data"`
   600  		MD   apc.WritePolicy `json:"md"`
   601  	}
   602  	WritePolicyConfToSet struct {
   603  		Data *apc.WritePolicy `json:"data,omitempty" list:"readonly"` // NOTE: NIY
   604  		MD   *apc.WritePolicy `json:"md,omitempty"`
   605  	}
   606  )
   607  
   608  // assorted named fields that require (cluster | node) restart for changes to make an effect
   609  var ConfigRestartRequired = []string{"auth", "memsys", "net"}
   610  
   611  // dsort
   612  const (
   613  	IgnoreReaction = "ignore"
   614  	WarnReaction   = "warn"
   615  	AbortReaction  = "abort"
   616  )
   617  
   618  var SupportedReactions = []string{IgnoreReaction, WarnReaction, AbortReaction}
   619  
   620  //
   621  // config meta-versioning & serialization
   622  //
   623  
   624  var (
   625  	_ jsp.Opts = (*ClusterConfig)(nil)
   626  	_ jsp.Opts = (*LocalConfig)(nil)
   627  	_ jsp.Opts = (*ConfigToSet)(nil)
   628  )
   629  
   630  func _jspOpts() jsp.Options {
   631  	opts := jsp.CCSign(MetaverConfig)
   632  	opts.OldMetaverOk = 3
   633  	return opts
   634  }
   635  
   636  func (*LocalConfig) JspOpts() jsp.Options   { return jsp.Plain() }
   637  func (*ClusterConfig) JspOpts() jsp.Options { return _jspOpts() }
   638  func (*ConfigToSet) JspOpts() jsp.Options   { return _jspOpts() }
   639  
   640  // interface guard
   641  var (
   642  	_ Validator = (*BackendConf)(nil)
   643  	_ Validator = (*CksumConf)(nil)
   644  	_ Validator = (*LogConf)(nil)
   645  	_ Validator = (*LRUConf)(nil)
   646  	_ Validator = (*SpaceConf)(nil)
   647  	_ Validator = (*MirrorConf)(nil)
   648  	_ Validator = (*ECConf)(nil)
   649  	_ Validator = (*VersionConf)(nil)
   650  	_ Validator = (*KeepaliveConf)(nil)
   651  	_ Validator = (*PeriodConf)(nil)
   652  	_ Validator = (*TimeoutConf)(nil)
   653  	_ Validator = (*ClientConf)(nil)
   654  	_ Validator = (*RebalanceConf)(nil)
   655  	_ Validator = (*ResilverConf)(nil)
   656  	_ Validator = (*NetConf)(nil)
   657  	_ Validator = (*HTTPConf)(nil)
   658  	_ Validator = (*DownloaderConf)(nil)
   659  	_ Validator = (*DsortConf)(nil)
   660  	_ Validator = (*TransportConf)(nil)
   661  	_ Validator = (*MemsysConf)(nil)
   662  	_ Validator = (*TCBConf)(nil)
   663  	_ Validator = (*WritePolicyConf)(nil)
   664  
   665  	_ PropsValidator = (*CksumConf)(nil)
   666  	_ PropsValidator = (*SpaceConf)(nil)
   667  	_ PropsValidator = (*MirrorConf)(nil)
   668  	_ PropsValidator = (*ECConf)(nil)
   669  	_ PropsValidator = (*WritePolicyConf)(nil)
   670  
   671  	_ json.Marshaler   = (*BackendConf)(nil)
   672  	_ json.Unmarshaler = (*BackendConf)(nil)
   673  	_ json.Marshaler   = (*FSPConf)(nil)
   674  	_ json.Unmarshaler = (*FSPConf)(nil)
   675  )
   676  
   677  /////////////////////////////////////////////
   678  // Config and its nested (Cluster | Local) //
   679  /////////////////////////////////////////////
   680  
   681  // main config validator
   682  func (c *Config) Validate() error {
   683  	if c.ConfigDir == "" {
   684  		return errors.New("invalid confdir value (must be non-empty)")
   685  	}
   686  	if c.LogDir == "" {
   687  		return errors.New("invalid log dir value (must be non-empty)")
   688  	}
   689  
   690  	// NOTE: These two validations require more context and so we call them explicitly;
   691  	//       The rest all implement generic interface.
   692  	if err := c.LocalConfig.HostNet.Validate(c); err != nil {
   693  		return err
   694  	}
   695  	if err := c.LocalConfig.FSP.Validate(c); err != nil {
   696  		return err
   697  	}
   698  	if err := c.LocalConfig.TestFSP.Validate(c); err != nil {
   699  		return err
   700  	}
   701  
   702  	opts := IterOpts{VisitAll: true}
   703  	return IterFields(c, vdate, opts)
   704  }
   705  
   706  func vdate(_ string, field IterField) (error, bool) {
   707  	if v, ok := field.Value().(Validator); ok {
   708  		if err := v.Validate(); err != nil {
   709  			return err, false
   710  		}
   711  	}
   712  	return nil, false
   713  }
   714  
   715  func (c *Config) SetRole(role string) {
   716  	debug.Assert(role == apc.Target || role == apc.Proxy)
   717  	c.role = role
   718  }
   719  
   720  func (c *Config) UpdateClusterConfig(updateConf *ConfigToSet, asType string) (err error) {
   721  	err = c.ClusterConfig.Apply(updateConf, asType)
   722  	if err != nil {
   723  		return
   724  	}
   725  	return c.Validate()
   726  }
   727  
   728  // TestingEnv returns true if config is set to a development environment
   729  // where a single local filesystem is partitioned between all (locally running)
   730  // targets and is used for both local and Cloud buckets
   731  // See also: `rom.testingEnv`
   732  func (c *Config) TestingEnv() bool {
   733  	return c.LocalConfig.TestingEnv()
   734  }
   735  
   736  ///////////////////
   737  // ClusterConfig //
   738  ///////////////////
   739  
   740  func (c *ClusterConfig) Apply(updateConf *ConfigToSet, asType string) error {
   741  	return copyProps(updateConf, c, asType)
   742  }
   743  
   744  func (c *ClusterConfig) String() string {
   745  	if c == nil {
   746  		return "Conf <nil>"
   747  	}
   748  	return fmt.Sprintf("Conf v%d[%s]", c.Version, c.UUID)
   749  }
   750  
   751  /////////////////
   752  // LocalConfig //
   753  /////////////////
   754  
   755  func (c *LocalConfig) TestingEnv() bool {
   756  	return c.TestFSP.Count > 0
   757  }
   758  
   759  func (c *LocalConfig) AddPath(mpath string) {
   760  	debug.Assert(!c.TestingEnv())
   761  	c.FSP.Paths[mpath] = ""
   762  }
   763  
   764  func (c *LocalConfig) DelPath(mpath string) {
   765  	debug.Assert(!c.TestingEnv())
   766  	c.FSP.Paths.Delete(mpath)
   767  }
   768  
   769  ////////////////
   770  // PeriodConf //
   771  ////////////////
   772  
   773  func (c *PeriodConf) Validate() error {
   774  	if c.StatsTime.D() < time.Second || c.StatsTime.D() > time.Minute {
   775  		return fmt.Errorf("invalid periodic.stats_time=%s (expected range [1s, 1m])",
   776  			c.StatsTime)
   777  	}
   778  	if c.RetrySyncTime.D() < 10*time.Millisecond || c.RetrySyncTime.D() > 10*time.Second {
   779  		return fmt.Errorf("invalid periodic.retry_sync_time=%s (expected range [10ms, 10s])",
   780  			c.StatsTime)
   781  	}
   782  	if c.NotifTime.D() < time.Second || c.NotifTime.D() > time.Minute {
   783  		return fmt.Errorf("invalid periodic.notif_time=%s (expected range [1s, 1m])",
   784  			c.StatsTime)
   785  	}
   786  	return nil
   787  }
   788  
   789  /////////////
   790  // LogConf //
   791  /////////////
   792  
   793  func (c *LogConf) Validate() error {
   794  	if err := c.Level.Validate(); err != nil {
   795  		return err
   796  	}
   797  	if c.MaxSize < cos.KiB || c.MaxSize > cos.GiB {
   798  		return fmt.Errorf("invalid log.max_size=%s (expected range [1KB, 1GB])", c.MaxSize)
   799  	}
   800  	if c.MaxTotal < cos.MiB || c.MaxTotal > 10*cos.GiB {
   801  		return fmt.Errorf("invalid log.max_total=%s (expected range [1MB, 10GB])", c.MaxTotal)
   802  	}
   803  	if c.MaxSize > c.MaxTotal/2 {
   804  		return fmt.Errorf("invalid log.max_total=%s, must be >= 2*(log.max_size=%s)", c.MaxTotal, c.MaxSize)
   805  	}
   806  	if c.FlushTime.D() > time.Hour {
   807  		return fmt.Errorf("invalid log.flush_time=%s (expected range [0, 1h)", c.FlushTime)
   808  	}
   809  	if c.StatsTime.D() > 10*time.Minute {
   810  		return fmt.Errorf("invalid log.stats_time=%s (expected range [log.stats_time, 10m])", c.StatsTime)
   811  	}
   812  	return nil
   813  }
   814  
   815  ////////////////
   816  // ClientConf //
   817  ////////////////
   818  
   819  func (c *ClientConf) Validate() error {
   820  	if j := c.Timeout.D(); j < time.Second || j > 2*time.Minute {
   821  		return fmt.Errorf("invalid client.client_timeout=%s (expected range [1s, 2m])", j)
   822  	}
   823  	if j := c.TimeoutLong.D(); j < 30*time.Second || j < c.Timeout.D() || j > 30*time.Minute {
   824  		return fmt.Errorf("invalid client.client_long_timeout=%s (expected range [30s, 30m])", j)
   825  	}
   826  	if j := c.ListObjTimeout.D(); j < 2*time.Second || j > 15*time.Minute {
   827  		return fmt.Errorf("invalid client.list_timeout=%s (expected range [2s, 15m])", j)
   828  	}
   829  	return nil
   830  }
   831  
   832  /////////////////
   833  // BackendConf //
   834  /////////////////
   835  
   836  func (c *BackendConf) keys() (v []string) {
   837  	for k := range c.Conf {
   838  		v = append(v, k)
   839  	}
   840  	return
   841  }
   842  
   843  func (c *BackendConf) UnmarshalJSON(data []byte) error {
   844  	return jsoniter.Unmarshal(data, &c.Conf)
   845  }
   846  
   847  func (c *BackendConf) MarshalJSON() (data []byte, err error) {
   848  	return cos.MustMarshal(c.Conf), nil
   849  }
   850  
   851  func (c *BackendConf) Validate() (err error) {
   852  	for provider := range c.Conf {
   853  		b := cos.MustMarshal(c.Conf[provider])
   854  		switch provider {
   855  		case apc.AIS:
   856  			var aisConf BackendConfAIS
   857  			if err := jsoniter.Unmarshal(b, &aisConf); err != nil {
   858  				return fmt.Errorf("invalid cloud specification: %v", err)
   859  			}
   860  			for alias, urls := range aisConf {
   861  				if len(urls) == 0 {
   862  					return fmt.Errorf("no URL(s) to connect to remote AIS cluster %q", alias)
   863  				}
   864  			}
   865  			c.Conf[provider] = aisConf
   866  		case "":
   867  			continue
   868  		default:
   869  			c.setProvider(provider)
   870  		}
   871  	}
   872  	return nil
   873  }
   874  
   875  func (c *BackendConf) setProvider(provider string) {
   876  	var ns Ns
   877  	switch provider {
   878  	case apc.AWS, apc.Azure, apc.GCP:
   879  		ns = NsGlobal
   880  	default:
   881  		debug.Assert(false, "unknown backend provider "+provider)
   882  	}
   883  	if c.Providers == nil {
   884  		c.Providers = map[string]Ns{}
   885  	}
   886  	c.Providers[provider] = ns
   887  }
   888  
   889  func (c *BackendConf) Get(provider string) (conf any) {
   890  	if c, ok := c.Conf[provider]; ok {
   891  		conf = c
   892  	}
   893  	return
   894  }
   895  
   896  func (c *BackendConf) Set(provider string, newConf any) {
   897  	c.Conf[provider] = newConf
   898  }
   899  
   900  func (c *BackendConf) EqualClouds(o *BackendConf) bool {
   901  	if len(o.Conf) != len(c.Conf) {
   902  		return false
   903  	}
   904  	for k := range o.Conf {
   905  		if _, ok := c.Conf[k]; !ok {
   906  			return false
   907  		}
   908  	}
   909  	return true
   910  }
   911  
   912  func (c *BackendConf) EqualRemAIS(o *BackendConf, sname string) bool {
   913  	var oldRemotes, newRemotes BackendConfAIS
   914  	oais, oko := o.Conf[apc.AIS]
   915  	nais, okn := c.Conf[apc.AIS]
   916  	if !oko && !okn {
   917  		return true
   918  	}
   919  	if oko != okn {
   920  		return false
   921  	}
   922  	erro := cos.MorphMarshal(oais, &oldRemotes)
   923  	errn := cos.MorphMarshal(nais, &newRemotes)
   924  	if erro != nil || errn != nil {
   925  		nlog.Errorf("%s: failed to unmarshal remote AIS backends: %v, %v", sname, erro, errn)
   926  		debug.AssertNoErr(errn)
   927  		return errn != nil // "equal" when cannot make use
   928  	}
   929  	if len(oldRemotes) != len(newRemotes) {
   930  		return false
   931  	}
   932  	for k := range oldRemotes {
   933  		if _, ok := newRemotes[k]; !ok {
   934  			return false
   935  		}
   936  	}
   937  	return true
   938  }
   939  
   940  func (c BackendConfAIS) String() (s string) {
   941  	for a, urls := range c {
   942  		if s != "" {
   943  			s += "; "
   944  		}
   945  		s += fmt.Sprintf("[%s => %v]", a, urls)
   946  	}
   947  	return
   948  }
   949  
   950  //////////////
   951  // DiskConf //
   952  //////////////
   953  
   954  func (c *DiskConf) Validate() (err error) {
   955  	lwm, hwm, maxwm := c.DiskUtilLowWM, c.DiskUtilHighWM, c.DiskUtilMaxWM
   956  	if lwm <= 0 || hwm <= lwm || maxwm <= hwm || maxwm > 100 {
   957  		return fmt.Errorf("invalid (disk_util_lwm, disk_util_hwm, disk_util_maxwm) config %+v", c)
   958  	}
   959  	if c.IostatTimeLong <= 0 {
   960  		return errors.New("disk.iostat_time_long is zero")
   961  	}
   962  	if c.IostatTimeShort <= 0 {
   963  		return errors.New("disk.iostat_time_short is zero")
   964  	}
   965  	if c.IostatTimeLong < c.IostatTimeShort {
   966  		return fmt.Errorf("disk.iostat_time_long %v shorter than disk.iostat_time_short %v",
   967  			c.IostatTimeLong, c.IostatTimeShort)
   968  	}
   969  	return nil
   970  }
   971  
   972  ///////////////
   973  // SpaceConf //
   974  ///////////////
   975  
   976  func (c *SpaceConf) Validate() (err error) {
   977  	if c.CleanupWM <= 0 || c.LowWM < c.CleanupWM || c.HighWM < c.LowWM || c.OOS < c.HighWM || c.OOS > 100 {
   978  		err = fmt.Errorf("invalid %s (expecting: 0 < cleanup < low < high < OOS < 100)", c)
   979  	}
   980  	return
   981  }
   982  
   983  func (c *SpaceConf) ValidateAsProps(...any) error { return c.Validate() }
   984  
   985  func (c *SpaceConf) String() string {
   986  	return fmt.Sprintf("space config: cleanup=%d%%, low=%d%%, high=%d%%, OOS=%d%%",
   987  		c.CleanupWM, c.LowWM, c.HighWM, c.OOS)
   988  }
   989  
   990  /////////////
   991  // LRUConf //
   992  /////////////
   993  
   994  func (c *LRUConf) String() string {
   995  	if !c.Enabled {
   996  		return "Disabled"
   997  	}
   998  	return fmt.Sprintf("lru.dont_evict_time=%v, lru.capacity_upd_time=%v", c.DontEvictTime, c.CapacityUpdTime)
   999  }
  1000  
  1001  func (c *LRUConf) Validate() (err error) {
  1002  	if c.CapacityUpdTime.D() < 10*time.Second {
  1003  		err = fmt.Errorf("invalid %s (expecting: lru.capacity_upd_time >= 10s)", c)
  1004  	}
  1005  	return
  1006  }
  1007  
  1008  ///////////////
  1009  // CksumConf //
  1010  ///////////////
  1011  
  1012  func (c *CksumConf) Validate() (err error) {
  1013  	return cos.ValidateCksumType(c.Type)
  1014  }
  1015  
  1016  func (c *CksumConf) ValidateAsProps(...any) (err error) {
  1017  	return c.Validate()
  1018  }
  1019  
  1020  func (c *CksumConf) String() string {
  1021  	if c.Type == cos.ChecksumNone {
  1022  		return "Disabled"
  1023  	}
  1024  
  1025  	toValidate := make([]string, 0)
  1026  	add := func(val bool, name string) {
  1027  		if val {
  1028  			toValidate = append(toValidate, name)
  1029  		}
  1030  	}
  1031  	add(c.ValidateColdGet, "ColdGET")
  1032  	add(c.ValidateWarmGet, "WarmGET")
  1033  	add(c.ValidateObjMove, "ObjectMove")
  1034  	add(c.EnableReadRange, "ReadRange")
  1035  
  1036  	toValidateStr := "Nothing"
  1037  	if len(toValidate) > 0 {
  1038  		toValidateStr = strings.Join(toValidate, ",")
  1039  	}
  1040  
  1041  	return fmt.Sprintf("Type: %s | Validate: %s", c.Type, toValidateStr)
  1042  }
  1043  
  1044  /////////////////
  1045  // VersionConf //
  1046  /////////////////
  1047  
  1048  func (c *VersionConf) Validate() error {
  1049  	if !c.Enabled && c.ValidateWarmGet {
  1050  		return errors.New("versioning.validate_warm_get requires versioning to be enabled")
  1051  	}
  1052  	return nil
  1053  }
  1054  
  1055  func (c *VersionConf) String() string {
  1056  	if !c.Enabled {
  1057  		return "Disabled"
  1058  	}
  1059  
  1060  	text := "Enabled | Validate on WarmGET: "
  1061  	if c.ValidateWarmGet {
  1062  		text += "yes"
  1063  	} else {
  1064  		text += "no"
  1065  	}
  1066  
  1067  	return text
  1068  }
  1069  
  1070  ////////////////
  1071  // MirrorConf //
  1072  ////////////////
  1073  
  1074  func (c *MirrorConf) Validate() error {
  1075  	if c.Burst < 0 {
  1076  		return fmt.Errorf("invalid mirror.burst_buffer: %v (expected >0)", c.Burst)
  1077  	}
  1078  	if c.Copies < 2 || c.Copies > 32 {
  1079  		return fmt.Errorf("invalid mirror.copies: %d (expected value in range [2, 32])", c.Copies)
  1080  	}
  1081  	return nil
  1082  }
  1083  
  1084  func (c *MirrorConf) ValidateAsProps(...any) error {
  1085  	if !c.Enabled {
  1086  		return nil
  1087  	}
  1088  	return c.Validate()
  1089  }
  1090  
  1091  func (c *MirrorConf) String() string {
  1092  	if !c.Enabled {
  1093  		return "Disabled"
  1094  	}
  1095  
  1096  	return fmt.Sprintf("%d copies", c.Copies)
  1097  }
  1098  
  1099  ////////////
  1100  // ECConf //
  1101  ////////////
  1102  
  1103  const (
  1104  	ObjSizeToAlwaysReplicate = -1 // (see `ObjSizeLimit` comment above)
  1105  
  1106  	minSliceCount = 1  // minimum number of data or parity slices
  1107  	maxSliceCount = 32 // maximum --/--
  1108  )
  1109  
  1110  func (c *ECConf) Validate() error {
  1111  	if c.ObjSizeLimit < -1 {
  1112  		return fmt.Errorf("invalid ec.obj_size_limit: %d (expecting greater or equal -1)", c.ObjSizeLimit)
  1113  	}
  1114  	if c.DataSlices < minSliceCount || c.DataSlices > maxSliceCount {
  1115  		err := fmt.Errorf("invalid ec.data_slices: %d (expected value in range [%d, %d])",
  1116  			c.DataSlices, minSliceCount, maxSliceCount)
  1117  		return err
  1118  	}
  1119  	if c.ParitySlices < minSliceCount || c.ParitySlices > maxSliceCount {
  1120  		return fmt.Errorf("invalid ec.parity_slices: %d (expected value in range [%d, %d])",
  1121  			c.ParitySlices, minSliceCount, maxSliceCount)
  1122  	}
  1123  	if c.SbundleMult < 0 || c.SbundleMult > 16 {
  1124  		return fmt.Errorf("invalid ec.bundle_multiplier: %v (expected range [0, 16])", c.SbundleMult)
  1125  	}
  1126  	if !apc.IsValidCompression(c.Compression) {
  1127  		return fmt.Errorf("invalid ec.compression: %q (expecting one of: %v)", c.Compression, apc.SupportedCompression)
  1128  	}
  1129  	return nil
  1130  }
  1131  
  1132  func (c *ECConf) ValidateAsProps(arg ...any) (err error) {
  1133  	if !c.Enabled {
  1134  		return
  1135  	}
  1136  	if err = c.Validate(); err != nil {
  1137  		return
  1138  	}
  1139  	targetCnt, ok := arg[0].(int)
  1140  	debug.Assert(ok)
  1141  	required := c.numRequiredTargets()
  1142  	if required <= targetCnt {
  1143  		return
  1144  	}
  1145  
  1146  	err = fmt.Errorf("%v: EC configuration (D = %d, P = %d) requires at least %d targets (have %d)",
  1147  		ErrNotEnoughTargets, c.DataSlices, c.ParitySlices, required, targetCnt)
  1148  	if c.ObjSizeLimit == ObjSizeToAlwaysReplicate || c.ParitySlices > targetCnt {
  1149  		return
  1150  	}
  1151  	return NewErrSoft(err.Error())
  1152  }
  1153  
  1154  func (c *ECConf) String() string {
  1155  	if !c.Enabled {
  1156  		return "Disabled"
  1157  	}
  1158  	objSizeLimit := c.ObjSizeLimit
  1159  	if objSizeLimit == ObjSizeToAlwaysReplicate {
  1160  		return fmt.Sprintf("no EC - always producing %d total replicas", c.ParitySlices+1)
  1161  	}
  1162  	return fmt.Sprintf("%d:%d (objsize limit %s)", c.DataSlices, c.ParitySlices, cos.ToSizeIEC(objSizeLimit, 0))
  1163  }
  1164  
  1165  func (c *ECConf) numRequiredTargets() int {
  1166  	if c.ObjSizeLimit == ObjSizeToAlwaysReplicate {
  1167  		return c.ParitySlices + 1
  1168  	}
  1169  	// (data slices + parity slices + 1 target for the _main_ replica)
  1170  	return c.DataSlices + c.ParitySlices + 1
  1171  }
  1172  
  1173  func (c *ECConf) RequiredRestoreTargets() int {
  1174  	return c.DataSlices
  1175  }
  1176  
  1177  /////////////////////
  1178  // WritePolicyConf //
  1179  /////////////////////
  1180  
  1181  func (c *WritePolicyConf) Validate() (err error) {
  1182  	err = c.Data.Validate()
  1183  	if err == nil {
  1184  		if !c.Data.IsImmediate() {
  1185  			return fmt.Errorf("invalid write policy for data: %q not implemented yet", c.Data)
  1186  		}
  1187  		err = c.MD.Validate()
  1188  	}
  1189  	return
  1190  }
  1191  
  1192  func (c *WritePolicyConf) ValidateAsProps(...any) error { return c.Validate() }
  1193  
  1194  ///////////////////
  1195  // KeepaliveConf //
  1196  ///////////////////
  1197  
  1198  func (c *KeepaliveConf) Validate() (err error) {
  1199  	if c.Proxy.Name != "heartbeat" {
  1200  		err = fmt.Errorf("invalid keepalivetracker.proxy.name %s", c.Proxy.Name)
  1201  	} else if c.Target.Name != "heartbeat" {
  1202  		err = fmt.Errorf("invalid keepalivetracker.target.name %s", c.Target.Name)
  1203  	} else if c.RetryFactor < 1 || c.RetryFactor > 10 {
  1204  		err = fmt.Errorf("invalid keepalivetracker.retry_factor %d (expecting 1 thru 10)", c.RetryFactor)
  1205  	}
  1206  	return
  1207  }
  1208  
  1209  func KeepaliveRetryDuration(c *Config) time.Duration {
  1210  	d := c.Timeout.CplaneOperation.D() * time.Duration(c.Keepalive.RetryFactor)
  1211  	return min(d, c.Timeout.MaxKeepalive.D()+time.Second/2)
  1212  }
  1213  
  1214  /////////////
  1215  // NetConf //
  1216  /////////////
  1217  
  1218  func (c *NetConf) Validate() (err error) {
  1219  	if c.L4.Proto != "tcp" {
  1220  		return fmt.Errorf("l4 proto %q is not recognized (expecting %s)", c.L4.Proto, "tcp")
  1221  	}
  1222  	c.HTTP.Proto = "http" // not validating: read-only, and can take only two values
  1223  	if c.HTTP.UseHTTPS {
  1224  		c.HTTP.Proto = "https"
  1225  	}
  1226  	if c.HTTP.ClientAuthTLS < int(tls.NoClientCert) || c.HTTP.ClientAuthTLS > int(tls.RequireAndVerifyClientCert) {
  1227  		return fmt.Errorf("invalid client_auth_tls %d (expecting range [0 - %d])", c.HTTP.ClientAuthTLS,
  1228  			tls.RequireAndVerifyClientCert)
  1229  	}
  1230  	return nil
  1231  }
  1232  
  1233  func (c *HTTPConf) Validate() error {
  1234  	if c.ServerNameTLS != "" {
  1235  		return fmt.Errorf("invalid domain_tls %q: expecting empty (domain names/SANs should be set in X.509 cert)", c.ServerNameTLS)
  1236  	}
  1237  	return nil
  1238  }
  1239  
  1240  // used intra-clients; see related: EnvToTLS()
  1241  func (c *HTTPConf) ToTLS() TLSArgs {
  1242  	return TLSArgs{
  1243  		Certificate: c.Certificate,
  1244  		Key:         c.CertKey,
  1245  		ClientCA:    c.ClientCA,
  1246  		SkipVerify:  c.SkipVerifyCrt,
  1247  	}
  1248  }
  1249  
  1250  ////////////////////
  1251  // LocalNetConfig //
  1252  ////////////////////
  1253  
  1254  const HostnameListSepa = ","
  1255  
  1256  func (c *LocalNetConfig) Validate(contextConfig *Config) (err error) {
  1257  	c.Hostname = strings.ReplaceAll(c.Hostname, " ", "")
  1258  	c.HostnameIntraControl = strings.ReplaceAll(c.HostnameIntraControl, " ", "")
  1259  	c.HostnameIntraData = strings.ReplaceAll(c.HostnameIntraData, " ", "")
  1260  
  1261  	if addr, over := ipsOverlap(c.Hostname, c.HostnameIntraControl); over {
  1262  		return fmt.Errorf("public (%s) and intra-cluster control (%s) share the same: %q",
  1263  			c.Hostname, c.HostnameIntraControl, addr)
  1264  	}
  1265  	if addr, over := ipsOverlap(c.Hostname, c.HostnameIntraData); over {
  1266  		return fmt.Errorf("public (%s) and intra-cluster data (%s) share the same: %q",
  1267  			c.Hostname, c.HostnameIntraData, addr)
  1268  	}
  1269  	if addr, over := ipsOverlap(c.HostnameIntraControl, c.HostnameIntraData); over {
  1270  		if ipv4ListsEqual(c.HostnameIntraControl, c.HostnameIntraData) {
  1271  			nlog.Warningln("control and data share the same intra-cluster network:", c.HostnameIntraData)
  1272  		} else {
  1273  			nlog.Warningf("intra-cluster control (%s) and data (%s) share the same: %q",
  1274  				c.HostnameIntraControl, c.HostnameIntraData, addr)
  1275  		}
  1276  	}
  1277  
  1278  	// Parse ports
  1279  	if _, err := ValidatePort(c.Port); err != nil {
  1280  		return fmt.Errorf("invalid %s port specified: %v", NetPublic, err)
  1281  	}
  1282  	if c.PortIntraControl != 0 {
  1283  		if _, err := ValidatePort(c.PortIntraControl); err != nil {
  1284  			return fmt.Errorf("invalid %s port specified: %v", NetIntraControl, err)
  1285  		}
  1286  	}
  1287  	if c.PortIntraData != 0 {
  1288  		if _, err := ValidatePort(c.PortIntraData); err != nil {
  1289  			return fmt.Errorf("invalid %s port specified: %v", NetIntraData, err)
  1290  		}
  1291  	}
  1292  
  1293  	// NOTE: intra-cluster networks
  1294  	differentIPs := c.Hostname != c.HostnameIntraControl
  1295  	differentPorts := c.Port != c.PortIntraControl
  1296  	c.UseIntraControl = (contextConfig.TestingEnv() || c.HostnameIntraControl != "") &&
  1297  		c.PortIntraControl != 0 && (differentIPs || differentPorts)
  1298  
  1299  	differentIPs = c.Hostname != c.HostnameIntraData
  1300  	differentPorts = c.Port != c.PortIntraData
  1301  	c.UseIntraData = (contextConfig.TestingEnv() || c.HostnameIntraData != "") &&
  1302  		c.PortIntraData != 0 && (differentIPs || differentPorts)
  1303  	return
  1304  }
  1305  
  1306  ///////////////
  1307  // DsortConf //
  1308  ///////////////
  1309  
  1310  const _idsort = "invalid distributed_sort."
  1311  
  1312  func (c *DsortConf) Validate() (err error) {
  1313  	if c.SbundleMult < 0 || c.SbundleMult > 16 {
  1314  		return fmt.Errorf(_idsort+"bundle_multiplier: %v (expected range [0, 16])", c.SbundleMult)
  1315  	}
  1316  	if !apc.IsValidCompression(c.Compression) {
  1317  		return fmt.Errorf(_idsort+"compression: %q (expecting one of: %v)", c.Compression, apc.SupportedCompression)
  1318  	}
  1319  	return c.ValidateWithOpts(false)
  1320  }
  1321  
  1322  func (c *DsortConf) ValidateWithOpts(allowEmpty bool) (err error) {
  1323  	checkReaction := func(reaction string) bool {
  1324  		return cos.StringInSlice(reaction, SupportedReactions) || (allowEmpty && reaction == "")
  1325  	}
  1326  
  1327  	if !checkReaction(c.DuplicatedRecords) {
  1328  		return fmt.Errorf(_idsort+"duplicated_records: %s (expecting one of: %s)", c.DuplicatedRecords, SupportedReactions)
  1329  	}
  1330  	if !checkReaction(c.MissingShards) {
  1331  		return fmt.Errorf(_idsort+"missing_shards: %s (expecting one of: %s)", c.MissingShards, SupportedReactions)
  1332  	}
  1333  	if !checkReaction(c.EKMMalformedLine) {
  1334  		return fmt.Errorf(_idsort+"ekm_malformed_line: %s (expecting one of: %s)", c.EKMMalformedLine, SupportedReactions)
  1335  	}
  1336  	if !checkReaction(c.EKMMissingKey) {
  1337  		return fmt.Errorf(_idsort+"ekm_missing_key: %s (expecting one of: %s)", c.EKMMissingKey, SupportedReactions)
  1338  	}
  1339  	if !allowEmpty {
  1340  		if _, err := cos.ParseQuantity(c.DefaultMaxMemUsage); err != nil {
  1341  			return fmt.Errorf(_idsort+"default_max_mem_usage: %s (err: %s)", c.DefaultMaxMemUsage, err)
  1342  		}
  1343  	}
  1344  	if _, err := cos.ParseSize(c.DsorterMemThreshold, cos.UnitsIEC); err != nil && (!allowEmpty || c.DsorterMemThreshold != "") {
  1345  		return fmt.Errorf(_idsort+"dsorter_mem_threshold: %s (err: %s)", c.DsorterMemThreshold, err)
  1346  	}
  1347  	return nil
  1348  }
  1349  
  1350  /////////////
  1351  // FSPConf //
  1352  /////////////
  1353  
  1354  var AllowSharedDisksAndNoDisks bool // NOTE: deprecated; keeping it strictly for backward compatibility
  1355  
  1356  func (c *FSPConf) UnmarshalJSON(data []byte) error {
  1357  	m := cos.NewStrKVs(10)
  1358  	err := jsoniter.Unmarshal(data, &m)
  1359  	if err == nil {
  1360  		c.Paths = m
  1361  		return nil
  1362  	}
  1363  	// [backward compatibility] try loading from the prev. meta-version
  1364  	var v322 FSPConfV322
  1365  	v322.Paths = make(cos.StrSet, 10)
  1366  	if err = jsoniter.Unmarshal(data, &v322.Paths); err == nil {
  1367  		for fspath := range v322.Paths {
  1368  			m[fspath] = ""
  1369  		}
  1370  		c.Paths = m
  1371  		// cannot nlog yet - in the process of loading config (w/ log dirs not yet assigned)
  1372  		fmt.Fprintln(os.Stderr, "Warning: load fspaths from V3 (older) config:", c.Paths)
  1373  	}
  1374  	return err
  1375  }
  1376  
  1377  func (c *FSPConf) MarshalJSON() ([]byte, error) {
  1378  	return cos.MustMarshal(c.Paths), nil
  1379  }
  1380  
  1381  func (c *FSPConf) Validate(contextConfig *Config) error {
  1382  	debug.Assertf(cos.StringInSlice(contextConfig.role, []string{apc.Proxy, apc.Target}),
  1383  		"unexpected role: %q", contextConfig.role)
  1384  
  1385  	// Don't validate in testing environment.
  1386  	if contextConfig.TestingEnv() || contextConfig.role != apc.Target {
  1387  		return nil
  1388  	}
  1389  	if len(c.Paths) == 0 {
  1390  		return NewErrInvalidFSPathsConf(ErrNoMountpaths)
  1391  	}
  1392  
  1393  	cleanMpaths := cos.NewStrKVs(len(c.Paths))
  1394  	for fspath, val := range c.Paths {
  1395  		mpath, err := ValidateMpath(fspath)
  1396  		if err != nil {
  1397  			return err
  1398  		}
  1399  		l := len(mpath)
  1400  		// disallow mountpath nesting
  1401  		for mpath2 := range cleanMpaths {
  1402  			if mpath2 == mpath {
  1403  				err := fmt.Errorf("%q (%q) is duplicated", mpath, fspath)
  1404  				return NewErrInvalidFSPathsConf(err)
  1405  			}
  1406  			if err := IsNestedMpath(mpath, l, mpath2); err != nil {
  1407  				return NewErrInvalidFSPathsConf(err)
  1408  			}
  1409  		}
  1410  		cleanMpaths[mpath] = val
  1411  	}
  1412  	c.Paths = cleanMpaths
  1413  	return nil
  1414  }
  1415  
  1416  func IsNestedMpath(a string, la int, b string) (err error) {
  1417  	const fmterr = "mountpath nesting is not permitted: %q contains %q"
  1418  	lb := len(b)
  1419  	if la > lb {
  1420  		if a[0:lb] == b && a[lb] == filepath.Separator {
  1421  			err = fmt.Errorf(fmterr, a, b)
  1422  		}
  1423  	} else if la < lb {
  1424  		if b[0:la] == a && b[la] == filepath.Separator {
  1425  			err = fmt.Errorf(fmterr, b, a)
  1426  		}
  1427  	}
  1428  	return
  1429  }
  1430  
  1431  /////////////////
  1432  // FSPConfV322 //
  1433  /////////////////
  1434  
  1435  // [backward compatibility]: used to generate fspath config for older ais versions
  1436  func (c *FSPConfV322) MarshalJSON() ([]byte, error) {
  1437  	return cos.MustMarshal(c.Paths), nil
  1438  }
  1439  
  1440  /////////////////
  1441  // TestFSPConf //
  1442  /////////////////
  1443  
  1444  // validate root and (NOTE: testing only) generate and fill-in counted FSP.Paths
  1445  func (c *TestFSPConf) Validate(contextConfig *Config) (err error) {
  1446  	// Don't validate in production environment.
  1447  	if !contextConfig.TestingEnv() || contextConfig.role != apc.Target {
  1448  		return nil
  1449  	}
  1450  
  1451  	cleanMpath, err := ValidateMpath(c.Root)
  1452  	if err != nil {
  1453  		return err
  1454  	}
  1455  	c.Root = cleanMpath
  1456  
  1457  	contextConfig.FSP.Paths = cos.NewStrKVs(c.Count)
  1458  	for i := range c.Count {
  1459  		mpath := filepath.Join(c.Root, fmt.Sprintf("mp%d", i+1))
  1460  		if c.Instance > 0 {
  1461  			mpath = filepath.Join(mpath, strconv.Itoa(c.Instance))
  1462  		}
  1463  		contextConfig.FSP.Paths[mpath] = ""
  1464  	}
  1465  	return nil
  1466  }
  1467  
  1468  func (c *TestFSPConf) ValidateMpath(p string) (err error) {
  1469  	debug.Assert(c.Count > 0)
  1470  	for i := range c.Count {
  1471  		mpath := filepath.Join(c.Root, fmt.Sprintf("mp%d", i+1))
  1472  		if c.Instance > 0 {
  1473  			mpath = filepath.Join(mpath, strconv.Itoa(c.Instance))
  1474  		}
  1475  		if strings.HasPrefix(p, mpath) {
  1476  			return
  1477  		}
  1478  	}
  1479  	err = fmt.Errorf("%q does not appear to be a valid testing mountpath, where (root=%q, count=%d)",
  1480  		p, c.Root, c.Count)
  1481  	return
  1482  }
  1483  
  1484  // common mountpath validation (NOTE: calls filepath.Clean() every time)
  1485  func ValidateMpath(mpath string) (string, error) {
  1486  	cleanMpath := filepath.Clean(mpath)
  1487  
  1488  	if cleanMpath[0] != filepath.Separator {
  1489  		return mpath, NewErrInvalidaMountpath(mpath, "mountpath must be an absolute path")
  1490  	}
  1491  	if cleanMpath == cos.PathSeparator {
  1492  		return "", NewErrInvalidaMountpath(mpath, "root directory is not a valid mountpath")
  1493  	}
  1494  	return cleanMpath, nil
  1495  }
  1496  
  1497  ////////////////
  1498  // MemsysConf //
  1499  ////////////////
  1500  
  1501  func (c *MemsysConf) Validate() (err error) {
  1502  	if c.MinFree > 0 && c.MinFree < 100*cos.MiB {
  1503  		return fmt.Errorf("invalid memsys.min_free %s (cannot be less than 100MB, optimally at least 2GB)", c.MinFree)
  1504  	}
  1505  	if c.DefaultBufSize > 128*cos.KiB {
  1506  		return fmt.Errorf("invalid memsys.default_buf %s (must be a multiple of 4KB in range [4KB, 128KB]", c.DefaultBufSize)
  1507  	}
  1508  	if c.DefaultBufSize%(4*cos.KiB) != 0 {
  1509  		return fmt.Errorf("memsys.default_buf %s must a multiple of 4KB", c.DefaultBufSize)
  1510  	}
  1511  	if c.SizeToGC > cos.TiB {
  1512  		return fmt.Errorf("invalid memsys.to_gc %s (expected range [0, 1TB))", c.SizeToGC)
  1513  	}
  1514  	if c.HousekeepTime.D() > time.Hour {
  1515  		return fmt.Errorf("invalid memsys.hk_time %s (expected range [0, 1h))", c.HousekeepTime)
  1516  	}
  1517  	if c.MinPctTotal < 0 || c.MinPctTotal > 95 {
  1518  		return fmt.Errorf("invalid memsys.min_pct_total %d%%", c.MinPctTotal)
  1519  	}
  1520  	if c.MinPctFree < 0 || c.MinPctFree > 95 {
  1521  		return fmt.Errorf("invalid memsys.min_pct_free %d%%", c.MinPctFree)
  1522  	}
  1523  	return nil
  1524  }
  1525  
  1526  ///////////////////
  1527  // TransportConf //
  1528  ///////////////////
  1529  
  1530  // NOTE: uncompressed block sizes - the enum currently supported by the github.com/pierrec/lz4
  1531  func (c *TransportConf) Validate() (err error) {
  1532  	if c.LZ4BlockMaxSize != 64*cos.KiB && c.LZ4BlockMaxSize != 256*cos.KiB &&
  1533  		c.LZ4BlockMaxSize != cos.MiB && c.LZ4BlockMaxSize != 4*cos.MiB {
  1534  		return fmt.Errorf("invalid transport.block_size %s (expected one of: [64K, 256K, 1MB, 4MB])",
  1535  			c.LZ4BlockMaxSize)
  1536  	}
  1537  	if c.Burst < 0 {
  1538  		return fmt.Errorf("invalid transport.burst_buffer: %v (expected >0)", c.Burst)
  1539  	}
  1540  	if c.MaxHeaderSize < 0 {
  1541  		return fmt.Errorf("invalid transport.max_header: %v (expected >0)", c.MaxHeaderSize)
  1542  	}
  1543  	if c.IdleTeardown.D() < time.Second {
  1544  		return fmt.Errorf("invalid transport.idle_teardown: %v (expected >= 1s)", c.IdleTeardown)
  1545  	}
  1546  	if c.QuiesceTime.D() < 8*time.Second {
  1547  		return fmt.Errorf("invalid transport.quiescent: %v (expected >= 8s)", c.QuiesceTime)
  1548  	}
  1549  	if c.MaxHeaderSize > 0 && c.MaxHeaderSize < 512 {
  1550  		return fmt.Errorf("invalid transport.max_header: %v (expected >= 512)", c.MaxHeaderSize)
  1551  	}
  1552  	return nil
  1553  }
  1554  
  1555  /////////////
  1556  // TCBConf //
  1557  /////////////
  1558  
  1559  func (c *TCBConf) Validate() error {
  1560  	if c.SbundleMult < 0 || c.SbundleMult > 16 {
  1561  		return fmt.Errorf("invalid tcb.bundle_multiplier: %v (expected range [0, 16])", c.SbundleMult)
  1562  	}
  1563  	if !apc.IsValidCompression(c.Compression) {
  1564  		return fmt.Errorf("invalid tcb.compression: %q (expecting one of: %v)",
  1565  			c.Compression, apc.SupportedCompression)
  1566  	}
  1567  	return nil
  1568  }
  1569  
  1570  /////////////////
  1571  // TimeoutConf //
  1572  /////////////////
  1573  
  1574  func (c *TimeoutConf) Validate() error {
  1575  	if c.CplaneOperation.D() < 10*time.Millisecond {
  1576  		return fmt.Errorf("invalid timeout.cplane_operation=%s", c.CplaneOperation)
  1577  	}
  1578  	if c.MaxKeepalive < 2*c.CplaneOperation {
  1579  		return fmt.Errorf("invalid timeout.max_keepalive=%s, must be >= 2*(cplane_operation=%s)",
  1580  			c.MaxKeepalive, c.CplaneOperation)
  1581  	}
  1582  	if c.MaxHostBusy.D() < 10*time.Second {
  1583  		return fmt.Errorf("invalid timeout.max_host_busy=%s (cannot be less than 10s)", c.MaxHostBusy)
  1584  	}
  1585  	if c.Startup.D() < 30*time.Second {
  1586  		return fmt.Errorf("invalid timeout.startup_time=%s (cannot be less than 30s)", c.Startup)
  1587  	}
  1588  	if c.JoinAtStartup != 0 && c.JoinAtStartup < 2*c.Startup {
  1589  		return fmt.Errorf("invalid timeout.join_startup_time=%s, must be >= 2*(timeout.startup_time=%s)",
  1590  			c.JoinAtStartup, c.Startup)
  1591  	}
  1592  	if c.SendFile.D() < time.Minute {
  1593  		return fmt.Errorf("invalid timeout.send_file_time=%s (cannot be less than 1m)", c.SendFile)
  1594  	}
  1595  	return nil
  1596  }
  1597  
  1598  ////////////////////
  1599  // DownloaderConf //
  1600  ////////////////////
  1601  
  1602  func (c *DownloaderConf) Validate() error {
  1603  	if j := c.Timeout.D(); j < time.Second || j > time.Hour {
  1604  		return fmt.Errorf("invalid downloader.timeout=%s (expected range [1s, 1h])", j)
  1605  	}
  1606  	return nil
  1607  }
  1608  
  1609  ///////////////////
  1610  // RebalanceConf //
  1611  ///////////////////
  1612  
  1613  func (c *RebalanceConf) Validate() error {
  1614  	if j := c.DestRetryTime.D(); j < time.Second || j > 10*time.Minute {
  1615  		return fmt.Errorf("invalid rebalance.dest_retry_time=%s (expected range [1s, 10m])", j)
  1616  	}
  1617  	if c.SbundleMult < 0 || c.SbundleMult > 16 {
  1618  		return fmt.Errorf("invalid rebalance.bundle_multiplier: %v (expected range [0, 16])", c.SbundleMult)
  1619  	}
  1620  	if !apc.IsValidCompression(c.Compression) {
  1621  		return fmt.Errorf("invalid rebalance.compression: %q (expecting one of: %v)",
  1622  			c.Compression, apc.SupportedCompression)
  1623  	}
  1624  	return nil
  1625  }
  1626  
  1627  func (c *RebalanceConf) String() string {
  1628  	if c.Enabled {
  1629  		return "Enabled"
  1630  	}
  1631  	return "Disabled"
  1632  }
  1633  
  1634  func (*ResilverConf) Validate() error { return nil }
  1635  
  1636  func (c *ResilverConf) String() string {
  1637  	if c.Enabled {
  1638  		return "Enabled"
  1639  	}
  1640  	return "Disabled"
  1641  }
  1642  
  1643  ////////////////////
  1644  // ConfigToSet //
  1645  ////////////////////
  1646  
  1647  // FillFromQuery populates ConfigToSet from URL query values
  1648  func (ctu *ConfigToSet) FillFromQuery(query url.Values) error {
  1649  	var anyExists bool
  1650  	for key := range query {
  1651  		if key == apc.ActTransient {
  1652  			continue
  1653  		}
  1654  		anyExists = true
  1655  		name, value := strings.ToLower(key), query.Get(key)
  1656  		if err := UpdateFieldValue(ctu, name, value); err != nil {
  1657  			return err
  1658  		}
  1659  	}
  1660  
  1661  	if !anyExists {
  1662  		return errors.New("no properties to update")
  1663  	}
  1664  	return nil
  1665  }
  1666  
  1667  func (ctu *ConfigToSet) Merge(update *ConfigToSet) {
  1668  	mergeProps(update, ctu)
  1669  }
  1670  
  1671  // FillFromKVS populates `ConfigToSet` from key value pairs of the form `key=value`
  1672  func (ctu *ConfigToSet) FillFromKVS(kvs []string) (err error) {
  1673  	const format = "failed to parse `-config_custom` flag (invalid entry: %q)"
  1674  	for _, kv := range kvs {
  1675  		entry := strings.SplitN(kv, "=", 2)
  1676  		if len(entry) != 2 {
  1677  			return fmt.Errorf(format, kv)
  1678  		}
  1679  		name, value := entry[0], entry[1]
  1680  		if err := UpdateFieldValue(ctu, name, value); err != nil {
  1681  			return fmt.Errorf(format, kv)
  1682  		}
  1683  	}
  1684  	return
  1685  }
  1686  
  1687  //
  1688  // misc config utils
  1689  //
  1690  
  1691  // checks if the two comma-separated IPv4 address lists contain at least one common IPv4
  1692  func ipsOverlap(alist, blist string) (addr string, overlap bool) {
  1693  	if alist == "" || blist == "" {
  1694  		return
  1695  	}
  1696  	alistAddrs := strings.Split(alist, HostnameListSepa)
  1697  	for _, a := range alistAddrs {
  1698  		a = strings.TrimSpace(a)
  1699  		if a == "" {
  1700  			continue
  1701  		}
  1702  		if strings.Contains(blist, a) {
  1703  			return a, true
  1704  		}
  1705  	}
  1706  	return
  1707  }
  1708  
  1709  func ipv4ListsEqual(alist, blist string) bool {
  1710  	alistAddrs := strings.Split(alist, ",")
  1711  	blistAddrs := strings.Split(blist, ",")
  1712  	f := func(in []string) (out []string) {
  1713  		out = make([]string, 0, len(in))
  1714  		for _, i := range in {
  1715  			i = strings.TrimSpace(i)
  1716  			if i == "" {
  1717  				continue
  1718  			}
  1719  			out = append(out, i)
  1720  		}
  1721  		return
  1722  	}
  1723  	al := f(alistAddrs)
  1724  	bl := f(blistAddrs)
  1725  	if len(al) == 0 || len(bl) == 0 || len(al) != len(bl) {
  1726  		return false
  1727  	}
  1728  	return cos.StrSlicesEqual(al, bl)
  1729  }
  1730  
  1731  // is called at startup
  1732  func LoadConfig(globalConfPath, localConfPath, daeRole string, config *Config) error {
  1733  	debug.Assert(globalConfPath != "" && localConfPath != "")
  1734  	GCO.SetInitialGconfPath(globalConfPath)
  1735  
  1736  	// first, local config
  1737  	if _, err := jsp.LoadMeta(localConfPath, &config.LocalConfig); err != nil {
  1738  		return fmt.Errorf("failed to load plain-text local config %q: %v", localConfPath, err) // FATAL
  1739  	}
  1740  	nlog.SetLogDirRole(config.LogDir, daeRole)
  1741  
  1742  	// Global (aka Cluster) config
  1743  	// Normally, when the node is being deployed the very first time the last updated version
  1744  	// of the config doesn't exist.
  1745  	// In this case, we load the initial plain-text global config from the command-line/environment
  1746  	// specified `globalConfPath`.
  1747  	// Once started, the node then always relies on the last updated version stored in a binary
  1748  	// form (in accordance with the associated ClusterConfig.JspOpts()).
  1749  	globalFpath := filepath.Join(config.ConfigDir, fname.GlobalConfig)
  1750  	if _, err := jsp.LoadMeta(globalFpath, &config.ClusterConfig); err != nil {
  1751  		if !os.IsNotExist(err) {
  1752  			if _, ok := err.(*jsp.ErrUnsupportedMetaVersion); ok {
  1753  				fmt.Fprintf(os.Stderr, "ERROR: "+FmtErrBackwardCompat+"\n", err)
  1754  			}
  1755  			return fmt.Errorf("failed to load global config %q: %v", globalConfPath, err)
  1756  		}
  1757  
  1758  		// initial plain-text
  1759  		const itxt = "load initial global config"
  1760  		_, err = jsp.Load(globalConfPath, &config.ClusterConfig, jsp.Plain())
  1761  		if err != nil {
  1762  			return fmt.Errorf("failed to %s %q: %v", itxt, globalConfPath, err)
  1763  		}
  1764  		if !config.TestingEnv() {
  1765  			fmt.Fprintln(os.Stderr, itxt, globalConfPath)
  1766  		}
  1767  		debug.Assert(config.Version == 0, config.Version)
  1768  		globalFpath = globalConfPath
  1769  	} else {
  1770  		debug.Assert(config.Version > 0 && config.UUID != "")
  1771  	}
  1772  
  1773  	// Set up logging.
  1774  	nlog.Setup(config.Log.ToStderr, int64(config.Log.MaxSize))
  1775  
  1776  	// initialize atomic part of the config including most often used timeouts and features
  1777  	Rom.Set(&config.ClusterConfig)
  1778  
  1779  	// read-only
  1780  	Rom.testingEnv = config.TestingEnv()
  1781  	config.SetRole(daeRole)
  1782  
  1783  	// override config - locally updated global defaults
  1784  	if err := handleOverrideConfig(config); err != nil {
  1785  		return err
  1786  	}
  1787  
  1788  	// create dirs
  1789  	if err := cos.CreateDir(config.LogDir); err != nil {
  1790  		return fmt.Errorf("failed to create log dir %q: %v", config.LogDir, err)
  1791  	}
  1792  	if config.TestingEnv() && daeRole == apc.Target {
  1793  		debug.Assert(config.TestFSP.Count == len(config.FSP.Paths))
  1794  		for mpath := range config.FSP.Paths {
  1795  			if err := cos.CreateDir(mpath); err != nil {
  1796  				return fmt.Errorf("failed to create %s mountpath in testing env: %v", mpath, err)
  1797  			}
  1798  		}
  1799  	}
  1800  
  1801  	// log header
  1802  	nlog.Infof("log.dir: %q; l4.proto: %s; pub port: %d; verbosity: %s",
  1803  		config.LogDir, config.Net.L4.Proto, config.HostNet.Port, config.Log.Level.String())
  1804  	nlog.Infof("config: %q; stats_time: %v; authentication: %t; backends: %v",
  1805  		globalFpath, config.Periodic.StatsTime, config.Auth.Enabled, config.Backend.keys())
  1806  	return nil
  1807  }
  1808  
  1809  func handleOverrideConfig(config *Config) error {
  1810  	overrideConfig, err := loadOverrideConfig(config.ConfigDir)
  1811  	if err != nil {
  1812  		if os.IsNotExist(err) {
  1813  			err = config.Validate() // always validate
  1814  		}
  1815  		return err
  1816  	}
  1817  
  1818  	// update config with locally-stored 'OverrideConfigFname' and validate the result
  1819  	GCO.PutOverride(overrideConfig)
  1820  	if overrideConfig.FSP != nil {
  1821  		config.LocalConfig.FSP = *overrideConfig.FSP // override local config's fspaths
  1822  		overrideConfig.FSP = nil
  1823  	}
  1824  	return config.UpdateClusterConfig(overrideConfig, apc.Daemon)
  1825  }
  1826  
  1827  func SaveOverrideConfig(configDir string, toUpdate *ConfigToSet) error {
  1828  	return jsp.SaveMeta(path.Join(configDir, fname.OverrideConfig), toUpdate, nil)
  1829  }
  1830  
  1831  func loadOverrideConfig(configDir string) (toUpdate *ConfigToSet, err error) {
  1832  	toUpdate = &ConfigToSet{}
  1833  	_, err = jsp.LoadMeta(path.Join(configDir, fname.OverrideConfig), toUpdate)
  1834  	return toUpdate, err
  1835  }
  1836  
  1837  func ValidateRemAlias(alias string) (err error) {
  1838  	if alias == apc.QparamWhat {
  1839  		return fmt.Errorf("cannot use %q as an alias", apc.QparamWhat)
  1840  	}
  1841  	if len(alias) < 2 {
  1842  		err = fmt.Errorf("alias %q is too short: must have at least 2 letters", alias)
  1843  	} else if !cos.IsAlphaPlus(alias) {
  1844  		err = fmt.Errorf("alias %q is invalid: use only letters, numbers, dashes (-), and underscores (_)", alias)
  1845  	}
  1846  	return
  1847  }