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 }