Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* [ENHANCEMENT] Store-gateway: added a `route` label to the `cortex_bucket_store_series_request_stage_duration_seconds` metric to match the `route` label of `cortex_request_duration_seconds`. #16374
* [ENHANCEMENT] Store-gateway: Add `force_attempt_http2` option to block storage client config to enable HTTP/2. #16385, #16422
* [ENHANCEMENT] Validation: Add an optional `reason` field to `limited_queries` rules, aligning them with `blocked_queries`. When set, the reason is included in the client-facing error and the query-frontend's `"query limited"` log line. #16407
* [ENHANCEMENT] Storage: Allow common subpath patterns containing slashes, hyphens, and underscores in storage prefixes while rejecting overlapping parent and child prefixes. #16479
* [ENHANCEMENT] Compactor: Add the experimental `-compactor.scheduler-client.enable-ring-based-cleanup` option, which when disabled stops a scheduler-mode compactor from running the ring-based background blocks cleaner. #16457
* [FEATURE] Querier: Add experimental per-tenant limit `-querier.max-blocks-per-store-request` to cap the number of blocks a single store-gateway request may reference. Disabled by default. #16292
* [FEATURE] Validation: Add optional `id`, `note`, `created_by`, `created_at`, and `expires_at` fields to `blocked_queries` and `limited_queries` rules, for tooling to attach ownership/context metadata to a rule. For rules with `expires_at` set, the earliest `expires_at` per tenant and `id` (rules without an `id` are grouped together) is exported as the `cortex_blocked_query_rule_expires_at`/`cortex_limited_query_rule_expires_at` metrics, so an alert can fire on stale rules; this is informational only and never affects enforcement. The query-frontend's `"query blocked"` log line now also includes the matched rule's `id` and whether it is expired, and rate-limited queries are now logged with a new `"query limited"` line carrying the same fields. #16395
Expand Down
6 changes: 3 additions & 3 deletions cmd/mimir/config-descriptor.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions cmd/mimir/help-all.txt.tmpl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions cmd/mimir/help.txt.tmpl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 9 additions & 6 deletions docs/sources/mimir/configure/configuration-parameters/index.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ When using the common configuration, make [`blocks_storage`](../configuration-pa

{{< /admonition >}}

Grafana Mimir will fail to start if you configure blocks storage to use the same bucket and storage prefix that the Alertmanager or ruler store uses.
Grafana Mimir will fail to start if you configure blocks storage to use the same bucket and a storage prefix that overlaps the Alertmanager or ruler storage prefix.

Find examples of setting up the different object stores below:

Expand Down
16 changes: 11 additions & 5 deletions pkg/mimir/mimir.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,24 +541,24 @@ func validateBucketConfig(cfg bucket.Config, blockStorageBucketCfg bucket.Config
return nil
}

if cfg.StoragePrefix != blockStorageBucketCfg.StoragePrefix {
if !storagePrefixesOverlap(cfg.StoragePrefix, blockStorageBucketCfg.StoragePrefix) {
return nil
}

switch cfg.Backend {
case bucket.S3:
if cfg.S3.BucketName == blockStorageBucketCfg.S3.BucketName {
return errors.New("S3 bucket name and storage prefix cannot be the same as the one used in blocks storage config")
return errors.New("S3 bucket name and storage prefix cannot overlap with the ones used in blocks storage config")
}

case bucket.GCS:
if cfg.GCS.BucketName == blockStorageBucketCfg.GCS.BucketName {
return errors.New("GCS bucket name and storage prefix cannot be the same as the one used in blocks storage config")
return errors.New("GCS bucket name and storage prefix cannot overlap with the ones used in blocks storage config")
}

case bucket.Azure:
if cfg.Azure.ContainerName == blockStorageBucketCfg.Azure.ContainerName && cfg.Azure.StorageAccountName == blockStorageBucketCfg.Azure.StorageAccountName {
return errors.New("Azure container and account names and storage prefix cannot be the same as the ones used in blocks storage config")
return errors.New("Azure container and account names and storage prefix cannot overlap with the ones used in blocks storage config")
}

// To keep it simple here we only check that container and project names are not the same.
Expand All @@ -567,12 +567,18 @@ func validateBucketConfig(cfg bucket.Config, blockStorageBucketCfg bucket.Config
// may have several configured endpoints.
case bucket.Swift:
if cfg.Swift.ContainerName == blockStorageBucketCfg.Swift.ContainerName && cfg.Swift.ProjectName == blockStorageBucketCfg.Swift.ProjectName {
return errors.New("Swift container and project names and storage prefix cannot be the same as the ones used in blocks storage config")
return errors.New("Swift container and project names and storage prefix cannot overlap with the ones used in blocks storage config")
}
}
return nil
}

func storagePrefixesOverlap(first, second string) bool {
return first == second ||
strings.HasPrefix(first, second+"/") ||
strings.HasPrefix(second, first+"/")
}

// validateFilesystemPaths checks all configured filesystem paths and return error if it finds two of them
// overlapping (Mimir expects all filesystem paths to not overlap).
func (c *Config) validateFilesystemPaths(logger log.Logger) error {
Expand Down
57 changes: 57 additions & 0 deletions pkg/mimir/mimir_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,40 @@ func TestConfigValidation(t *testing.T) {
},
expectedError: errInvalidBucketConfig,
},
{
name: "S3: should fail if storage prefixes overlap between alertmanager and blocks storage",
getTestConfig: func() *Config {
cfg := newDefaultConfig()
_ = cfg.Target.Set("all,alertmanager")

for _, bucketCfg := range []*bucket.Config{&cfg.BlocksStorage.Bucket, &cfg.AlertmanagerStorage.Config} {
bucketCfg.Backend = bucket.S3
bucketCfg.S3.BucketName = "b1"
bucketCfg.S3.Region = "r1"
}
cfg.BlocksStorage.Bucket.StoragePrefix = "team-a"
cfg.AlertmanagerStorage.StoragePrefix = "team-a/mimir_alertmanager"
return cfg
},
expectedError: errInvalidBucketConfig,
},
{
name: "S3: should pass if storage prefixes are sibling subpaths",
getTestConfig: func() *Config {
cfg := newDefaultConfig()
_ = cfg.Target.Set("all,alertmanager")

for _, bucketCfg := range []*bucket.Config{&cfg.BlocksStorage.Bucket, &cfg.AlertmanagerStorage.Config} {
bucketCfg.Backend = bucket.S3
bucketCfg.S3.BucketName = "b1"
bucketCfg.S3.Region = "r1"
}
cfg.BlocksStorage.Bucket.StoragePrefix = "team-a/mimir_blocks"
cfg.AlertmanagerStorage.StoragePrefix = "team-a/mimir_alertmanager"
return cfg
},
expectedError: nil,
},
{
name: "GCS: should fail if bucket name is shared between alertmanager and blocks storage",
getTestConfig: func() *Config {
Expand Down Expand Up @@ -758,6 +792,29 @@ func TestConfigValidation(t *testing.T) {
}
}

func TestStoragePrefixesOverlap(t *testing.T) {
testCases := map[string]struct {
first string
second string
expected bool
}{
"equal prefixes": {first: "team-a/mimir", second: "team-a/mimir", expected: true},
"first prefix is parent": {first: "team-a", second: "team-a/mimir", expected: true},
"second prefix is parent": {
first: "team-a/mimir", second: "team-a", expected: true,
},
"sibling prefixes": {first: "team-a/blocks", second: "team-a/ruler", expected: false},
"similar segment names": {first: "team-a", second: "team-alerts", expected: false},
"empty and named prefix": {first: "", second: "team-a", expected: false},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert.Equal(t, tc.expected, storagePrefixesOverlap(tc.first, tc.second))
})
}
}

// validCompartmentsConfig returns a config with a valid compartments setup (compartments and ingest
// storage enabled, a read-compartment-templated topic, and two read and write compartments).
func validCompartmentsConfig() *Config {
Expand Down
8 changes: 4 additions & 4 deletions pkg/storage/bucket/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ const (
// Filesystem is the value for the filesystem storage backend.
Filesystem = "filesystem"

// validPrefixCharactersRegex allows only alphanumeric characters to prevent subtle bugs and simplify validation
validPrefixCharactersRegex = `^[\da-zA-Z]+$`
// validPrefixCharactersRegex allows slash-separated path segments containing alphanumeric characters, hyphens, and underscores.
validPrefixCharactersRegex = `^[\da-zA-Z_-]+(?:/[\da-zA-Z_-]+)*$`
Comment thread
cursor[bot] marked this conversation as resolved.

// MimirInternalsPrefix is the bucket prefix under which all Mimir internal cluster-wide objects are stored.
// The object storage path delimiter (/) is appended to this prefix when building the full object path.
Expand All @@ -59,7 +59,7 @@ var (
SupportedBackends = []string{S3, GCS, Azure, Swift, Filesystem}

ErrUnsupportedStorageBackend = errors.New("unsupported storage backend")
ErrInvalidCharactersInStoragePrefix = errors.New("storage prefix contains invalid characters, it may only contain digits and English alphabet letters")
ErrInvalidCharactersInStoragePrefix = errors.New("storage prefix contains invalid characters, it may only contain slash-separated path segments made of digits, English alphabet letters, hyphens, and underscores")
)

type StorageBackendConfig struct {
Expand Down Expand Up @@ -142,7 +142,7 @@ func (cfg *Config) RegisterFlags(f *flag.FlagSet) {

func (cfg *Config) RegisterFlagsWithPrefixAndDefaultDirectory(prefix, dir string, f *flag.FlagSet) {
cfg.StorageBackendConfig.RegisterFlagsWithPrefixAndDefaultDirectory(prefix, dir, f)
f.StringVar(&cfg.StoragePrefix, prefix+"storage-prefix", "", "Prefix for all objects stored in the backend storage. For simplicity, it may only contain digits and English alphabet letters.")
f.StringVar(&cfg.StoragePrefix, prefix+"storage-prefix", "", "Prefix for all objects stored in the backend storage. It may only contain slash-separated path segments made of digits, English alphabet letters, hyphens, and underscores.")
}

func (cfg *Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
Expand Down
Loading