cfg/validate.go (211 lines of code) (raw):

// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cfg import ( "errors" "fmt" "math" "github.com/googlecloudplatform/gcsfuse/v2/internal/util" ) const ( FileCacheMaxSizeMBInvalidValueError = "the value of max-size-mb for file-cache can't be less than -1" MaxParallelDownloadsInvalidValueError = "the value of max-parallel-downloads for file-cache can't be less than -1" ParallelDownloadsPerFileInvalidValueError = "the value of parallel-downloads-per-file for file-cache can't be less than 1" DownloadChunkSizeMBInvalidValueError = "the value of download-chunk-size-mb for file-cache can't be less than 1" MaxParallelDownloadsCantBeZeroError = "the value of max-parallel-downloads for file-cache must not be 0 when enable-parallel-downloads is true" ) func isValidLogRotateConfig(config *LogRotateLoggingConfig) error { if config.MaxFileSizeMb <= 0 { return fmt.Errorf("max-file-size-mb should be atleast 1") } if config.BackupFileCount < 0 { return fmt.Errorf("backup-file-count should be 0 (to retain all backup files) or a positive value") } return nil } func isValidURL(u string) error { _, err := decodeURL(u) return err } func isValidParallelDownloadConfig(config *Config) error { if config.FileCache.EnableParallelDownloads { if !IsFileCacheEnabled(config) { return errors.New("file cache should be enabled for parallel download support") } if config.FileCache.MaxParallelDownloads == 0 { return errors.New("the value of max-parallel-downloads for file-cache must not be 0 when enable-parallel-downloads is true") } if config.FileCache.WriteBufferSize < CacheUtilMinimumAlignSizeForWriting { return errors.New("the value of write-buffer-size for file-cache can't be less than 4096") } if (config.FileCache.WriteBufferSize % CacheUtilMinimumAlignSizeForWriting) != 0 { return errors.New("the value of write-buffer-size for file-cache should be in multiple of 4096") } } return nil } func isValidFileCacheConfig(config *FileCacheConfig) error { if config.MaxSizeMb < -1 { return errors.New(FileCacheMaxSizeMBInvalidValueError) } if config.MaxParallelDownloads < -1 { return errors.New(MaxParallelDownloadsInvalidValueError) } if config.ParallelDownloadsPerFile < 1 { return errors.New(ParallelDownloadsPerFileInvalidValueError) } if config.DownloadChunkSizeMb < 1 { return errors.New(DownloadChunkSizeMBInvalidValueError) } return nil } func IsValidExperimentalMetadataPrefetchOnMount(mode string) error { switch mode { case ExperimentalMetadataPrefetchOnMountDisabled, ExperimentalMetadataPrefetchOnMountSynchronous, ExperimentalMetadataPrefetchOnMountAsynchronous: return nil default: return fmt.Errorf("unsupported metadata-prefix-mode: \"%s\"; supported values: disabled, sync, async", mode) } } func isValidSequentialReadSizeMB(size int64) error { if size < 1 || size > maxSequentialReadSizeMB { return fmt.Errorf("sequential-read-size-mb should be between 1 and %d", maxSequentialReadSizeMB) } return nil } // isTTLInSecsValid return nil error if ttlInSecs is valid. func isTTLInSecsValid(secs int64) error { if secs < -1 { return fmt.Errorf("the value of ttl-secs can't be less than -1") } if secs > maxSupportedTTLInSeconds { return fmt.Errorf("the value of ttl-secs is too high to be supported. Max is 9223372036") } return nil } func isValidKernelListCacheTTL(TTLSecs int64) error { if err := isTTLInSecsValid(TTLSecs); err != nil { return fmt.Errorf("invalid kernelListCacheTtlSecs: %w", err) } return nil } func isValidMetadataCache(v isSet, c *MetadataCacheConfig) error { // Validate ttl-secs. if v.IsSet(MetadataCacheTTLConfigKey) { if c.TtlSecs < -1 { return fmt.Errorf("the value of ttl-secs for metadata-cache can't be less than -1") } if c.TtlSecs > maxSupportedTTLInSeconds { return fmt.Errorf("the value of ttl-secs in metadata-cache is too high to be supported. Max is 9223372036") } } // Validate negative-ttl-secs. if v.IsSet(MetadataNegativeCacheTTLConfigKey) { if c.NegativeTtlSecs < -1 { return fmt.Errorf("the value of negative-ttl-secs for metadata-cache can't be less than -1") } if c.NegativeTtlSecs > maxSupportedTTLInSeconds { return fmt.Errorf("the value of negative-ttl-secs in metadata-cache is too high to be supported. Max is 9223372036") } } // Validate type-cache-max-size-mb. if c.TypeCacheMaxSizeMb < -1 { return fmt.Errorf("the value of type-cache-max-size-mb for metadata-cache can't be less than -1") } // Validate stat-cache-max-size-mb. if v.IsSet(StatCacheMaxSizeConfigKey) { if c.StatCacheMaxSizeMb < -1 { return fmt.Errorf("the value of stat-cache-max-size-mb for metadata-cache can't be less than -1") } if c.StatCacheMaxSizeMb > int64(maxSupportedStatCacheMaxSizeMB) { return fmt.Errorf("the value of stat-cache-max-size-mb for metadata-cache is too high! Max supported: 17592186044415") } } // [Deprecated] Validate stat-cache-capacity. if c.DeprecatedStatCacheCapacity < 0 { return fmt.Errorf("invalid value of stat-cache-capacity (%v), can't be less than 0", c.DeprecatedStatCacheCapacity) } return nil } func isValidWriteStreamingConfig(wc *WriteConfig) error { if !wc.EnableStreamingWrites { return nil } if wc.BlockSizeMb <= 0 || wc.BlockSizeMb > util.MaxMiBsInInt64 { return fmt.Errorf("invalid value of write-block-size-mb; can't be less than 1 or more than %d", util.MaxMiBsInInt64) } if !(wc.MaxBlocksPerFile == -1 || wc.MaxBlocksPerFile >= 1) { return fmt.Errorf("invalid value of write-max-blocks-per-file: %d; should be >=1 or -1 (for infinite)", wc.MaxBlocksPerFile) } if wc.GlobalMaxBlocks < -1 { return fmt.Errorf("invalid value of write-global-max-blocks: %d; should be >=0 or -1 (for infinite)", wc.GlobalMaxBlocks) } return nil } func isValidReadStallGcsRetriesConfig(rsrc *ReadStallGcsRetriesConfig) error { if rsrc == nil { return nil } if rsrc.Enable { if rsrc.ReqIncreaseRate <= 0 { return fmt.Errorf("invalid value of increaseRate: %f, can't be 0 or negative", rsrc.ReqIncreaseRate) } if rsrc.ReqTargetPercentile <= 0 || rsrc.ReqTargetPercentile >= 1 { return fmt.Errorf("invalid value of targetPercentile: %f, should be in the range (0, 1)", rsrc.ReqTargetPercentile) } } return nil } func isValidMetricsConfig(m *MetricsConfig) error { if m.StackdriverExportInterval != 0 && m.CloudMetricsExportIntervalSecs != 0 { return fmt.Errorf("exactly one of stackdriver-export-interval and cloud-metrics-export-interval-secs must be specified") } const maxPortNumber = math.MaxUint16 if m.PrometheusPort > maxPortNumber { return fmt.Errorf("prometheus-port must not be higher than the maximum allowed port number: %d but received: %d instead", maxPortNumber, m.PrometheusPort) } return nil } func isValidChunkTransferTimeoutForRetriesConfig(chunkTransferTimeoutSecs int64) error { if chunkTransferTimeoutSecs < 0 || chunkTransferTimeoutSecs > maxSupportedTTLInSeconds { return fmt.Errorf("invalid value of ChunkTransferTimeout: %d; should be > 0 or 0 (for infinite)", chunkTransferTimeoutSecs) } return nil } // ValidateConfig returns a non-nil error if the config is invalid. func ValidateConfig(v isSet, config *Config) error { var err error if err = isValidLogRotateConfig(&config.Logging.LogRotate); err != nil { return fmt.Errorf("error parsing log-rotate config: %w", err) } if err = isValidURL(config.GcsConnection.CustomEndpoint); err != nil { return fmt.Errorf("error parsing custom-endpoint config: %w", err) } if err = isValidFileCacheConfig(&config.FileCache); err != nil { return fmt.Errorf("error parsing file cache config: %w", err) } if err = IsValidExperimentalMetadataPrefetchOnMount(config.MetadataCache.ExperimentalMetadataPrefetchOnMount); err != nil { return fmt.Errorf("error parsing experimental-metadata-prefetch-on-mount: %w", err) } if err = isValidURL(config.GcsAuth.TokenUrl); err != nil { return fmt.Errorf("error parsing token-url config: %w", err) } if err = isValidSequentialReadSizeMB(config.GcsConnection.SequentialReadSizeMb); err != nil { return fmt.Errorf("error parsing gcs-connection config: %w", err) } if err = isValidKernelListCacheTTL(config.FileSystem.KernelListCacheTtlSecs); err != nil { return fmt.Errorf("error parsing kernel-list-cache-ttl-secs config: %w", err) } if err = isValidMetadataCache(v, &config.MetadataCache); err != nil { return fmt.Errorf("error parsing metadata-cache config: %w", err) } if err = isValidWriteStreamingConfig(&config.Write); err != nil { return fmt.Errorf("error parsing write config: %w", err) } if err = isValidReadStallGcsRetriesConfig(&config.GcsRetries.ReadStall); err != nil { return fmt.Errorf("error parsing read-stall-gcs-retries config: %w", err) } if err = isValidChunkTransferTimeoutForRetriesConfig(config.GcsRetries.ChunkTransferTimeoutSecs); err != nil { return fmt.Errorf("error parsing chunk-transfer-timeout-secs config: %w", err) } if err = isValidMetricsConfig(&config.Metrics); err != nil { return fmt.Errorf("error parsing metrics config: %w", err) } if err = isValidParallelDownloadConfig(config); err != nil { return fmt.Errorf("error parsing parallel download config: %w", err) } return nil }