github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/metrics/rules/validator/validator_test.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package validator
    22  
    23  import (
    24  	"fmt"
    25  	"math"
    26  	"strings"
    27  	"testing"
    28  
    29  	"github.com/m3db/m3/src/cluster/generated/proto/commonpb"
    30  	"github.com/m3db/m3/src/cluster/kv/mem"
    31  	"github.com/m3db/m3/src/metrics/aggregation"
    32  	"github.com/m3db/m3/src/metrics/errors"
    33  	"github.com/m3db/m3/src/metrics/filters"
    34  	"github.com/m3db/m3/src/metrics/metric"
    35  	"github.com/m3db/m3/src/metrics/pipeline"
    36  	"github.com/m3db/m3/src/metrics/policy"
    37  	"github.com/m3db/m3/src/metrics/rules/validator/namespace"
    38  	"github.com/m3db/m3/src/metrics/rules/validator/namespace/kv"
    39  	"github.com/m3db/m3/src/metrics/rules/view"
    40  	"github.com/m3db/m3/src/metrics/transformation"
    41  
    42  	"github.com/fortytw2/leaktest"
    43  	"github.com/stretchr/testify/assert"
    44  	"github.com/stretchr/testify/require"
    45  )
    46  
    47  const (
    48  	testTypeTag       = "type"
    49  	testCounterType   = "counter"
    50  	testTimerType     = "timer"
    51  	testGaugeType     = "gauge"
    52  	testNamespacesKey = "testNamespaces"
    53  )
    54  
    55  var (
    56  	testNamespaces = []string{"foo", "bar"}
    57  )
    58  
    59  func TestValidatorDefaultNamespaceValidator(t *testing.T) {
    60  	v := NewValidator(testValidatorOptions()).(*validator)
    61  
    62  	inputs := []string{"foo", "bar", "baz"}
    63  	for _, input := range inputs {
    64  		require.NoError(t, v.validateNamespace(input))
    65  	}
    66  }
    67  
    68  func TestValidatorInvalidNamespace(t *testing.T) {
    69  	defer leaktest.Check(t)()
    70  
    71  	nsValidator := testKVNamespaceValidator(t)
    72  	opts := testValidatorOptions().SetNamespaceValidator(nsValidator)
    73  	v := NewValidator(opts)
    74  	defer v.Close()
    75  
    76  	view := view.RuleSet{Namespace: "baz"}
    77  	require.Error(t, v.ValidateSnapshot(view))
    78  }
    79  
    80  func TestValidatorValidNamespace(t *testing.T) {
    81  	defer leaktest.Check(t)()
    82  
    83  	nsValidator := testKVNamespaceValidator(t)
    84  	opts := testValidatorOptions().SetNamespaceValidator(nsValidator)
    85  	v := NewValidator(opts)
    86  	defer v.Close()
    87  
    88  	view := view.RuleSet{Namespace: "foo"}
    89  	require.NoError(t, v.ValidateSnapshot(view))
    90  }
    91  
    92  func TestValidatorValidateDuplicateMappingRules(t *testing.T) {
    93  	view := view.RuleSet{
    94  		MappingRules: []view.MappingRule{
    95  			{
    96  				Name:            "snapshot1",
    97  				Filter:          "tag1:value1",
    98  				StoragePolicies: testStoragePolicies(),
    99  			},
   100  			{
   101  				Name:            "snapshot1",
   102  				Filter:          "tag1:value1",
   103  				StoragePolicies: testStoragePolicies(),
   104  			},
   105  		},
   106  	}
   107  	validator := NewValidator(testValidatorOptions())
   108  	err := validator.ValidateSnapshot(view)
   109  	require.Error(t, err)
   110  	_, ok := err.(errors.InvalidInputError)
   111  	require.True(t, ok)
   112  }
   113  
   114  func TestValidatorValidateNoDuplicateMappingRulesWithTombstone(t *testing.T) {
   115  	view := view.RuleSet{
   116  		MappingRules: []view.MappingRule{
   117  			{
   118  				Name:            "snapshot1",
   119  				Filter:          "tag1:value1",
   120  				Tombstoned:      true,
   121  				StoragePolicies: testStoragePolicies(),
   122  			},
   123  			{
   124  				Name:            "snapshot1",
   125  				Filter:          "tag1:value1",
   126  				StoragePolicies: testStoragePolicies(),
   127  			},
   128  		},
   129  	}
   130  
   131  	validator := NewValidator(testValidatorOptions())
   132  	require.NoError(t, validator.ValidateSnapshot(view))
   133  }
   134  
   135  func TestValidatorValidateMappingRuleInvalidFilterExpr(t *testing.T) {
   136  	view := view.RuleSet{
   137  		MappingRules: []view.MappingRule{
   138  			{
   139  				Name:   "snapshot1",
   140  				Filter: "randomTag:*too*many*wildcards*",
   141  			},
   142  		},
   143  	}
   144  	validator := NewValidator(testValidatorOptions())
   145  	require.Error(t, validator.ValidateSnapshot(view))
   146  }
   147  
   148  func TestValidatorValidateMappingRuleInvalidFilterTagName(t *testing.T) {
   149  	invalidChars := []rune{'$'}
   150  	view := view.RuleSet{
   151  		MappingRules: []view.MappingRule{
   152  			{
   153  				Name:   "snapshot1",
   154  				Filter: "random$Tag:foo",
   155  			},
   156  		},
   157  	}
   158  	validator := NewValidator(testValidatorOptions().SetTagNameInvalidChars(invalidChars))
   159  	require.Error(t, validator.ValidateSnapshot(view))
   160  }
   161  
   162  func TestValidatorValidateMappingRuleInvalidMetricType(t *testing.T) {
   163  	view := view.RuleSet{
   164  		MappingRules: []view.MappingRule{
   165  			{
   166  				Name:            "snapshot1",
   167  				Filter:          testTypeTag + ":nonexistent",
   168  				StoragePolicies: testStoragePolicies(),
   169  			},
   170  		},
   171  	}
   172  
   173  	validator := NewValidator(testValidatorOptions())
   174  	require.Error(t, validator.ValidateSnapshot(view))
   175  }
   176  
   177  func TestValidatorValidateMappingRuleInvalidAggregationType(t *testing.T) {
   178  	view := view.RuleSet{
   179  		MappingRules: []view.MappingRule{
   180  			{
   181  				Name:          "snapshot1",
   182  				Filter:        testTypeTag + ":" + testCounterType,
   183  				AggregationID: aggregation.ID{1234567789},
   184  			},
   185  		},
   186  	}
   187  
   188  	validator := NewValidator(testValidatorOptions())
   189  	require.Error(t, validator.ValidateSnapshot(view))
   190  }
   191  
   192  func TestValidatorValidateMappingRuleMultipleAggregationTypes(t *testing.T) {
   193  	testAggregationTypes := []aggregation.Type{aggregation.Count, aggregation.Max}
   194  	view := view.RuleSet{
   195  		MappingRules: []view.MappingRule{
   196  			{
   197  				Name:            "snapshot1",
   198  				Filter:          testTypeTag + ":" + testTimerType,
   199  				AggregationID:   aggregation.MustCompressTypes(aggregation.Count, aggregation.Max),
   200  				StoragePolicies: testStoragePolicies(),
   201  			},
   202  		},
   203  	}
   204  	inputs := []struct {
   205  		opts      Options
   206  		expectErr bool
   207  	}{
   208  		{
   209  			// By default multiple aggregation types are allowed for timers.
   210  			opts:      testValidatorOptions().SetAllowedFirstLevelAggregationTypesFor(metric.TimerType, testAggregationTypes),
   211  			expectErr: false,
   212  		},
   213  		{
   214  			// Explicitly disallow multiple aggregation types for timers.
   215  			opts:      testValidatorOptions().SetAllowedFirstLevelAggregationTypesFor(metric.TimerType, testAggregationTypes).SetMultiAggregationTypesEnabledFor(nil),
   216  			expectErr: true,
   217  		},
   218  	}
   219  
   220  	for _, input := range inputs {
   221  		validator := NewValidator(input.opts)
   222  		if input.expectErr {
   223  			require.Error(t, validator.ValidateSnapshot(view))
   224  		} else {
   225  			require.NoError(t, validator.ValidateSnapshot(view))
   226  		}
   227  	}
   228  }
   229  
   230  func TestValidatorValidateMappingRuleFirstLevelAggregationType(t *testing.T) {
   231  	testAggregationTypes := []aggregation.Type{aggregation.Count, aggregation.Max}
   232  	view := view.RuleSet{
   233  		MappingRules: []view.MappingRule{
   234  			{
   235  				Name:            "snapshot1",
   236  				Filter:          testTypeTag + ":" + testTimerType,
   237  				AggregationID:   aggregation.MustCompressTypes(aggregation.Count, aggregation.Max),
   238  				StoragePolicies: testStoragePolicies(),
   239  			},
   240  		},
   241  	}
   242  	inputs := []struct {
   243  		opts      Options
   244  		expectErr bool
   245  	}{
   246  		{
   247  			// By default no custom aggregation type is allowed.
   248  			opts:      testValidatorOptions(),
   249  			expectErr: true,
   250  		},
   251  		{
   252  			// Aggregation type is allowed through the default list of custom aggregation types.
   253  			opts:      testValidatorOptions().SetDefaultAllowedFirstLevelAggregationTypes(testAggregationTypes),
   254  			expectErr: false,
   255  		},
   256  		{
   257  			// Aggregation type is allowed through the list of custom aggregation types for timers.
   258  			opts:      testValidatorOptions().SetAllowedFirstLevelAggregationTypesFor(metric.TimerType, testAggregationTypes),
   259  			expectErr: false,
   260  		},
   261  	}
   262  
   263  	for _, input := range inputs {
   264  		validator := NewValidator(input.opts)
   265  		if input.expectErr {
   266  			require.Error(t, validator.ValidateSnapshot(view))
   267  		} else {
   268  			require.NoError(t, validator.ValidateSnapshot(view))
   269  		}
   270  	}
   271  }
   272  
   273  func TestValidatorValidateMappingRuleNoStoragePolicies(t *testing.T) {
   274  	view := view.RuleSet{
   275  		MappingRules: []view.MappingRule{
   276  			{
   277  				Name:   "snapshot1",
   278  				Filter: testTypeTag + ":" + testCounterType,
   279  			},
   280  		},
   281  	}
   282  
   283  	validator := NewValidator(testValidatorOptions())
   284  	err := validator.ValidateSnapshot(view)
   285  	require.Error(t, err)
   286  	require.True(t, strings.Contains(err.Error(), "no storage policies"))
   287  }
   288  
   289  func TestValidatorValidateMappingRuleDuplicateStoragePolicies(t *testing.T) {
   290  	view := view.RuleSet{
   291  		MappingRules: []view.MappingRule{
   292  			{
   293  				Name:   "snapshot1",
   294  				Filter: testTypeTag + ":" + testCounterType,
   295  				StoragePolicies: policy.StoragePolicies{
   296  					policy.MustParseStoragePolicy("10s:6h"),
   297  					policy.MustParseStoragePolicy("10s:6h"),
   298  				},
   299  			},
   300  		},
   301  	}
   302  
   303  	validator := NewValidator(testValidatorOptions())
   304  	err := validator.ValidateSnapshot(view)
   305  	require.Error(t, err)
   306  	require.True(t, strings.Contains(err.Error(), "duplicate storage policy '10s:6h'"))
   307  }
   308  
   309  func TestValidatorValidateMappingRuleDisallowedStoragePolicies(t *testing.T) {
   310  	view := view.RuleSet{
   311  		MappingRules: []view.MappingRule{
   312  			{
   313  				Name:   "snapshot1",
   314  				Filter: testTypeTag + ":" + testCounterType,
   315  				StoragePolicies: policy.StoragePolicies{
   316  					policy.MustParseStoragePolicy("1s:6h"),
   317  				},
   318  			},
   319  		},
   320  	}
   321  
   322  	validator := NewValidator(testValidatorOptions())
   323  	require.Error(t, validator.ValidateSnapshot(view))
   324  }
   325  
   326  func TestValidatorValidateMappingRule(t *testing.T) {
   327  	view := view.RuleSet{
   328  		MappingRules: []view.MappingRule{
   329  			{
   330  				Name:            "snapshot1",
   331  				Filter:          testTypeTag + ":" + testCounterType,
   332  				StoragePolicies: testStoragePolicies(),
   333  			},
   334  		},
   335  	}
   336  
   337  	validator := NewValidator(testValidatorOptions())
   338  	require.NoError(t, validator.ValidateSnapshot(view))
   339  }
   340  
   341  func TestValidatorValidateDuplicateRollupRules(t *testing.T) {
   342  	view := view.RuleSet{
   343  		RollupRules: []view.RollupRule{
   344  			{
   345  				Name:   "snapshot1",
   346  				Filter: "tag1:value1",
   347  			},
   348  			{
   349  				Name:   "snapshot1",
   350  				Filter: "tag1:value1",
   351  			},
   352  		},
   353  	}
   354  	validator := NewValidator(testValidatorOptions())
   355  	err := validator.ValidateSnapshot(view)
   356  	require.Error(t, err)
   357  	_, ok := err.(errors.InvalidInputError)
   358  	require.True(t, ok)
   359  }
   360  
   361  func TestValidatorValidateNoDuplicateRollupRulesWithTombstone(t *testing.T) {
   362  	rr1, err := pipeline.NewRollupOp(
   363  		pipeline.GroupByRollupType,
   364  		"rName1",
   365  		[]string{"rtagName1", "rtagName2"},
   366  		aggregation.DefaultID,
   367  	)
   368  	require.NoError(t, err)
   369  	rr2, err := pipeline.NewRollupOp(
   370  		pipeline.GroupByRollupType,
   371  		"rName1",
   372  		[]string{"rtagName1", "rtagName2"},
   373  		aggregation.DefaultID,
   374  	)
   375  	require.NoError(t, err)
   376  
   377  	view := view.RuleSet{
   378  		RollupRules: []view.RollupRule{
   379  			{
   380  				Name:       "snapshot1",
   381  				Filter:     "tag1:value1",
   382  				Tombstoned: true,
   383  				Targets: []view.RollupTarget{
   384  					{
   385  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   386  							{
   387  								Type:   pipeline.RollupOpType,
   388  								Rollup: rr1,
   389  							},
   390  						}),
   391  						StoragePolicies: testStoragePolicies(),
   392  					},
   393  				},
   394  			},
   395  			{
   396  				Name:   "snapshot1",
   397  				Filter: "tag1:value1",
   398  				Targets: []view.RollupTarget{
   399  					{
   400  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   401  							{
   402  								Type:   pipeline.RollupOpType,
   403  								Rollup: rr2,
   404  							},
   405  						}),
   406  						StoragePolicies: testStoragePolicies(),
   407  					},
   408  				},
   409  			},
   410  		},
   411  	}
   412  
   413  	validator := NewValidator(testValidatorOptions())
   414  	require.NoError(t, validator.ValidateSnapshot(view))
   415  }
   416  
   417  func TestValidatorValidateRollupRuleInvalidFilterExpr(t *testing.T) {
   418  	rr1, err := pipeline.NewRollupOp(
   419  		pipeline.GroupByRollupType,
   420  		"rName1",
   421  		[]string{"rtagName1", "rtagName2"},
   422  		aggregation.DefaultID,
   423  	)
   424  	require.NoError(t, err)
   425  
   426  	view := view.RuleSet{
   427  		RollupRules: []view.RollupRule{
   428  			{
   429  				Name:   "snapshot1",
   430  				Filter: "randomTag:*too*many*wildcards*",
   431  				Targets: []view.RollupTarget{
   432  					{
   433  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   434  							{
   435  								Type:   pipeline.RollupOpType,
   436  								Rollup: rr1,
   437  							},
   438  						}),
   439  						StoragePolicies: testStoragePolicies(),
   440  					},
   441  				},
   442  			},
   443  		},
   444  	}
   445  
   446  	validator := NewValidator(testValidatorOptions())
   447  	require.Error(t, validator.ValidateSnapshot(view))
   448  }
   449  
   450  func TestValidatorValidateRollupRuleInvalidFilterTagName(t *testing.T) {
   451  	rr1, err := pipeline.NewRollupOp(
   452  		pipeline.GroupByRollupType,
   453  		"rName1",
   454  		[]string{"rtagName1", "rtagName2"},
   455  		aggregation.DefaultID,
   456  	)
   457  	require.NoError(t, err)
   458  
   459  	invalidChars := []rune{'$'}
   460  	view := view.RuleSet{
   461  		RollupRules: []view.RollupRule{
   462  			{
   463  				Name:   "snapshot1",
   464  				Filter: "random$Tag:foo",
   465  				Targets: []view.RollupTarget{
   466  					{
   467  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   468  							{
   469  								Type:   pipeline.RollupOpType,
   470  								Rollup: rr1,
   471  							},
   472  						}),
   473  						StoragePolicies: testStoragePolicies(),
   474  					},
   475  				},
   476  			},
   477  		},
   478  	}
   479  	validator := NewValidator(testValidatorOptions().SetTagNameInvalidChars(invalidChars))
   480  	require.Error(t, validator.ValidateSnapshot(view))
   481  }
   482  
   483  func TestValidatorValidateRollupRuleInvalidMetricType(t *testing.T) {
   484  	rr1, err := pipeline.NewRollupOp(
   485  		pipeline.GroupByRollupType,
   486  		"rName1",
   487  		[]string{"rtagName1", "rtagName2"},
   488  		aggregation.DefaultID,
   489  	)
   490  	require.NoError(t, err)
   491  
   492  	view := view.RuleSet{
   493  		RollupRules: []view.RollupRule{
   494  			{
   495  				Name:   "snapshot1",
   496  				Filter: testTypeTag + ":nonexistent",
   497  				Targets: []view.RollupTarget{
   498  					{
   499  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   500  							{
   501  								Type:   pipeline.RollupOpType,
   502  								Rollup: rr1,
   503  							},
   504  						}),
   505  						StoragePolicies: testStoragePolicies(),
   506  					},
   507  				},
   508  			},
   509  		},
   510  	}
   511  	validator := NewValidator(testValidatorOptions())
   512  	require.Error(t, validator.ValidateSnapshot(view))
   513  }
   514  
   515  func TestValidatorValidateRollupRulePipelineEmptyPipeline(t *testing.T) {
   516  	view := view.RuleSet{
   517  		RollupRules: []view.RollupRule{
   518  			{
   519  				Name:   "snapshot1",
   520  				Filter: testTypeTag + ":" + testCounterType,
   521  				Targets: []view.RollupTarget{
   522  					{
   523  						Pipeline:        pipeline.NewPipeline([]pipeline.OpUnion{}),
   524  						StoragePolicies: testStoragePolicies(),
   525  					},
   526  				},
   527  			},
   528  		},
   529  	}
   530  	validator := NewValidator(testValidatorOptions())
   531  	err := validator.ValidateSnapshot(view)
   532  	require.Error(t, err)
   533  	require.True(t, strings.Contains(err.Error(), "empty pipeline"))
   534  }
   535  
   536  func TestValidatorValidateRollupRulePipelineInvalidPipelineOp(t *testing.T) {
   537  	view := view.RuleSet{
   538  		RollupRules: []view.RollupRule{
   539  			{
   540  				Name:   "snapshot1",
   541  				Filter: testTypeTag + ":" + testCounterType,
   542  				Targets: []view.RollupTarget{
   543  					{
   544  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   545  							{},
   546  						}),
   547  						StoragePolicies: testStoragePolicies(),
   548  					},
   549  				},
   550  			},
   551  		},
   552  	}
   553  	validator := NewValidator(testValidatorOptions())
   554  	err := validator.ValidateSnapshot(view)
   555  	require.Error(t, err)
   556  	require.True(t, strings.Contains(err.Error(), "operation at index 0 has invalid type"))
   557  }
   558  
   559  func TestValidatorValidateRollupRulePipelineMultipleAggregationOps(t *testing.T) {
   560  	view := view.RuleSet{
   561  		RollupRules: []view.RollupRule{
   562  			{
   563  				Name:   "snapshot1",
   564  				Filter: testTypeTag + ":" + testCounterType,
   565  				Targets: []view.RollupTarget{
   566  					{
   567  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   568  							{
   569  								Type:        pipeline.AggregationOpType,
   570  								Aggregation: pipeline.AggregationOp{Type: aggregation.Sum},
   571  							},
   572  							{
   573  								Type:        pipeline.AggregationOpType,
   574  								Aggregation: pipeline.AggregationOp{Type: aggregation.Sum},
   575  							},
   576  						}),
   577  						StoragePolicies: testStoragePolicies(),
   578  					},
   579  				},
   580  			},
   581  		},
   582  	}
   583  	allowedAggregationTypes := aggregation.Types{aggregation.Sum}
   584  	opts := testValidatorOptions().
   585  		SetAllowedFirstLevelAggregationTypesFor(metric.CounterType, allowedAggregationTypes)
   586  	validator := NewValidator(opts)
   587  	err := validator.ValidateSnapshot(view)
   588  	require.Error(t, err)
   589  	require.True(t, strings.Contains(err.Error(), "more than one aggregation operation in pipeline"))
   590  }
   591  
   592  func TestValidatorValidateRollupRulePipelineAggregationOpNotFirst(t *testing.T) {
   593  	view := view.RuleSet{
   594  		RollupRules: []view.RollupRule{
   595  			{
   596  				Name:   "snapshot1",
   597  				Filter: testTypeTag + ":" + testCounterType,
   598  				Targets: []view.RollupTarget{
   599  					{
   600  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   601  							{
   602  								Type:           pipeline.TransformationOpType,
   603  								Transformation: pipeline.TransformationOp{Type: transformation.PerSecond},
   604  							},
   605  							{
   606  								Type:        pipeline.AggregationOpType,
   607  								Aggregation: pipeline.AggregationOp{Type: aggregation.Sum},
   608  							},
   609  						}),
   610  						StoragePolicies: testStoragePolicies(),
   611  					},
   612  				},
   613  			},
   614  		},
   615  	}
   616  	allowedAggregationTypes := aggregation.Types{aggregation.Sum}
   617  	opts := testValidatorOptions().
   618  		SetAllowedFirstLevelAggregationTypesFor(metric.CounterType, allowedAggregationTypes)
   619  	validator := NewValidator(opts)
   620  	err := validator.ValidateSnapshot(view)
   621  	require.Error(t, err)
   622  	require.True(t, strings.Contains(err.Error(), "aggregation operation is not the first operation in pipeline"))
   623  }
   624  
   625  func TestValidatorValidateRollupRulePipelineAggregationOpInvalidAggregationType(t *testing.T) {
   626  	view := view.RuleSet{
   627  		RollupRules: []view.RollupRule{
   628  			{
   629  				Name:   "snapshot1",
   630  				Filter: testTypeTag + ":" + testCounterType,
   631  				Targets: []view.RollupTarget{
   632  					{
   633  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   634  							{
   635  								Type:        pipeline.AggregationOpType,
   636  								Aggregation: pipeline.AggregationOp{},
   637  							},
   638  						}),
   639  						StoragePolicies: testStoragePolicies(),
   640  					},
   641  				},
   642  			},
   643  		},
   644  	}
   645  	allowedAggregationTypes := aggregation.Types{aggregation.Sum}
   646  	opts := testValidatorOptions().
   647  		SetAllowedFirstLevelAggregationTypesFor(metric.CounterType, allowedAggregationTypes)
   648  	validator := NewValidator(opts)
   649  	err := validator.ValidateSnapshot(view)
   650  	require.Error(t, err)
   651  	require.True(t, strings.Contains(err.Error(), "invalid aggregation operation at index 0"))
   652  }
   653  
   654  func TestValidatorValidateRollupRulePipelineAggregationOpDisallowedAggregationType(t *testing.T) {
   655  	view := view.RuleSet{
   656  		RollupRules: []view.RollupRule{
   657  			{
   658  				Name:   "snapshot1",
   659  				Filter: testTypeTag + ":" + testCounterType,
   660  				Targets: []view.RollupTarget{
   661  					{
   662  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   663  							{
   664  								Type:        pipeline.AggregationOpType,
   665  								Aggregation: pipeline.AggregationOp{Type: aggregation.Sum},
   666  							},
   667  						}),
   668  						StoragePolicies: testStoragePolicies(),
   669  					},
   670  				},
   671  			},
   672  		},
   673  	}
   674  	validator := NewValidator(testValidatorOptions())
   675  	err := validator.ValidateSnapshot(view)
   676  	require.Error(t, err)
   677  	require.True(t, strings.Contains(err.Error(), "invalid aggregation operation at index 0"))
   678  }
   679  
   680  func TestValidatorValidateRollupRulePipelineTransformationDerivativeOrderNotSupported(t *testing.T) {
   681  	rr1, err := pipeline.NewRollupOp(
   682  		pipeline.GroupByRollupType,
   683  		"rName1",
   684  		[]string{"rtagName1", "rtagName2"},
   685  		aggregation.DefaultID,
   686  	)
   687  	require.NoError(t, err)
   688  
   689  	view := view.RuleSet{
   690  		RollupRules: []view.RollupRule{
   691  			{
   692  				Name:   "snapshot1",
   693  				Filter: testTypeTag + ":" + testCounterType,
   694  				Targets: []view.RollupTarget{
   695  					{
   696  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   697  							{
   698  								Type:   pipeline.RollupOpType,
   699  								Rollup: rr1,
   700  							},
   701  							{
   702  								Type:           pipeline.TransformationOpType,
   703  								Transformation: pipeline.TransformationOp{Type: transformation.PerSecond},
   704  							},
   705  							{
   706  								Type:           pipeline.TransformationOpType,
   707  								Transformation: pipeline.TransformationOp{Type: transformation.PerSecond},
   708  							},
   709  						}),
   710  						StoragePolicies: testStoragePolicies(),
   711  					},
   712  				},
   713  			},
   714  		},
   715  	}
   716  	validator := NewValidator(testValidatorOptions())
   717  	err = validator.ValidateSnapshot(view)
   718  	require.Error(t, err)
   719  	require.True(t, strings.Contains(err.Error(), "transformation derivative order is 2 higher than supported 1"))
   720  }
   721  
   722  func TestValidatorValidateRollupRulePipelineInvalidTransformationType(t *testing.T) {
   723  	view := view.RuleSet{
   724  		RollupRules: []view.RollupRule{
   725  			{
   726  				Name:   "snapshot1",
   727  				Filter: testTypeTag + ":" + testCounterType,
   728  				Targets: []view.RollupTarget{
   729  					{
   730  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   731  							{
   732  								Type:           pipeline.TransformationOpType,
   733  								Transformation: pipeline.TransformationOp{},
   734  							},
   735  						}),
   736  						StoragePolicies: testStoragePolicies(),
   737  					},
   738  				},
   739  			},
   740  		},
   741  	}
   742  	validator := NewValidator(testValidatorOptions())
   743  	err := validator.ValidateSnapshot(view)
   744  	require.Error(t, err)
   745  	require.True(t, strings.Contains(err.Error(), "invalid transformation operation at index 0"))
   746  }
   747  
   748  func TestValidatorValidateRollupRulePipelineNoRollupOp(t *testing.T) {
   749  	view := view.RuleSet{
   750  		RollupRules: []view.RollupRule{
   751  			{
   752  				Name:   "snapshot1",
   753  				Filter: testTypeTag + ":" + testCounterType,
   754  				Targets: []view.RollupTarget{
   755  					{
   756  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   757  							{
   758  								Type:           pipeline.TransformationOpType,
   759  								Transformation: pipeline.TransformationOp{Type: transformation.PerSecond},
   760  							},
   761  						}),
   762  						StoragePolicies: testStoragePolicies(),
   763  					},
   764  				},
   765  			},
   766  		},
   767  	}
   768  	validator := NewValidator(testValidatorOptions())
   769  	err := validator.ValidateSnapshot(view)
   770  	require.Error(t, err)
   771  	require.True(t, strings.Contains(err.Error(), "no rollup operation in pipeline"))
   772  }
   773  
   774  func TestValidatorValidateRollupRulePipelineRollupLevelHigherThanMax(t *testing.T) {
   775  	rr1, err := pipeline.NewRollupOp(
   776  		pipeline.GroupByRollupType,
   777  		"rName1",
   778  		[]string{"rtagName1", "rtagName2"},
   779  		aggregation.DefaultID,
   780  	)
   781  	require.NoError(t, err)
   782  	rr2, err := pipeline.NewRollupOp(
   783  		pipeline.GroupByRollupType,
   784  		"rName2",
   785  		[]string{"rtagName1", "rtagName2"},
   786  		aggregation.DefaultID,
   787  	)
   788  	require.NoError(t, err)
   789  
   790  	view := view.RuleSet{
   791  		RollupRules: []view.RollupRule{
   792  			{
   793  				Name:   "snapshot1",
   794  				Filter: testTypeTag + ":" + testCounterType,
   795  				Targets: []view.RollupTarget{
   796  					{
   797  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   798  							{
   799  								Type:   pipeline.RollupOpType,
   800  								Rollup: rr1,
   801  							},
   802  							{
   803  								Type:   pipeline.RollupOpType,
   804  								Rollup: rr2,
   805  							},
   806  						}),
   807  						StoragePolicies: testStoragePolicies(),
   808  					},
   809  				},
   810  			},
   811  		},
   812  	}
   813  	validator := NewValidator(testValidatorOptions())
   814  	err = validator.ValidateSnapshot(view)
   815  	require.Error(t, err)
   816  	require.True(t, strings.Contains(err.Error(), "number of rollup levels is 2 higher than supported 1"))
   817  }
   818  
   819  func TestValidatorValidateRollupRulePipelineRollupTagNotFoundInPrevRollupOp(t *testing.T) {
   820  	rr1, err := pipeline.NewRollupOp(
   821  		pipeline.GroupByRollupType,
   822  		"rName1",
   823  		[]string{"rtagName1", "rtagName2"},
   824  		aggregation.DefaultID,
   825  	)
   826  	require.NoError(t, err)
   827  	rr2, err := pipeline.NewRollupOp(
   828  		pipeline.GroupByRollupType,
   829  		"rName2",
   830  		[]string{"rtagName1", "rtagName2", "rtagName3"},
   831  		aggregation.DefaultID,
   832  	)
   833  	require.NoError(t, err)
   834  
   835  	view := view.RuleSet{
   836  		RollupRules: []view.RollupRule{
   837  			{
   838  				Name:   "snapshot1",
   839  				Filter: testTypeTag + ":" + testCounterType,
   840  				Targets: []view.RollupTarget{
   841  					{
   842  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   843  							{
   844  								Type:   pipeline.RollupOpType,
   845  								Rollup: rr1,
   846  							},
   847  							{
   848  								Type:   pipeline.RollupOpType,
   849  								Rollup: rr2,
   850  							},
   851  						}),
   852  						StoragePolicies: testStoragePolicies(),
   853  					},
   854  				},
   855  			},
   856  		},
   857  	}
   858  	validator := NewValidator(testValidatorOptions().SetMaxRollupLevels(100))
   859  	err = validator.ValidateSnapshot(view)
   860  	require.Error(t, err)
   861  	require.True(t, strings.Contains(err.Error(), "tag rtagName3 not found in previous rollup operations"))
   862  }
   863  
   864  func TestValidatorValidateRollupRulePipelineRollupTagUnchangedInConsecutiveRollupOps(t *testing.T) {
   865  	rr1, err := pipeline.NewRollupOp(
   866  		pipeline.GroupByRollupType,
   867  		"rName1",
   868  		[]string{"rtagName1", "rtagName2"},
   869  		aggregation.DefaultID,
   870  	)
   871  	require.NoError(t, err)
   872  	rr2, err := pipeline.NewRollupOp(
   873  		pipeline.GroupByRollupType,
   874  		"rName2",
   875  		[]string{"rtagName1", "rtagName2"},
   876  		aggregation.DefaultID,
   877  	)
   878  	require.NoError(t, err)
   879  	view := view.RuleSet{
   880  		RollupRules: []view.RollupRule{
   881  			{
   882  				Name:   "snapshot1",
   883  				Filter: testTypeTag + ":" + testCounterType,
   884  				Targets: []view.RollupTarget{
   885  					{
   886  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   887  							{
   888  								Type:   pipeline.RollupOpType,
   889  								Rollup: rr1,
   890  							},
   891  							{
   892  								Type:   pipeline.RollupOpType,
   893  								Rollup: rr2,
   894  							},
   895  						}),
   896  						StoragePolicies: testStoragePolicies(),
   897  					},
   898  				},
   899  			},
   900  		},
   901  	}
   902  	validator := NewValidator(testValidatorOptions().SetMaxRollupLevels(100))
   903  	err = validator.ValidateSnapshot(view)
   904  	require.Error(t, err)
   905  	require.True(t, strings.Contains(err.Error(), "same set of 2 rollup tags in consecutive rollup operations"))
   906  }
   907  
   908  func TestValidatorValidateRollupRulePipelineMultiLevelRollup(t *testing.T) {
   909  	rr1, err := pipeline.NewRollupOp(
   910  		pipeline.GroupByRollupType,
   911  		"rName1",
   912  		[]string{"rtagName1", "rtagName2", "rtagName3"},
   913  		aggregation.DefaultID,
   914  	)
   915  	require.NoError(t, err)
   916  	rr2, err := pipeline.NewRollupOp(
   917  		pipeline.GroupByRollupType,
   918  		"rName2",
   919  		[]string{"rtagName1", "rtagName2"},
   920  		aggregation.DefaultID,
   921  	)
   922  	require.NoError(t, err)
   923  	rr3, err := pipeline.NewRollupOp(
   924  		pipeline.GroupByRollupType,
   925  		"rName2",
   926  		[]string{"rtagName1"},
   927  		aggregation.DefaultID,
   928  	)
   929  	require.NoError(t, err)
   930  
   931  	view := view.RuleSet{
   932  		RollupRules: []view.RollupRule{
   933  			{
   934  				Name:   "snapshot1",
   935  				Filter: testTypeTag + ":" + testCounterType,
   936  				Targets: []view.RollupTarget{
   937  					{
   938  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   939  							{
   940  								Type:   pipeline.RollupOpType,
   941  								Rollup: rr1,
   942  							},
   943  							{
   944  								Type:   pipeline.RollupOpType,
   945  								Rollup: rr2,
   946  							},
   947  							{
   948  								Type:   pipeline.RollupOpType,
   949  								Rollup: rr3,
   950  							},
   951  						}),
   952  						StoragePolicies: testStoragePolicies(),
   953  					},
   954  				},
   955  			},
   956  		},
   957  	}
   958  	validator := NewValidator(testValidatorOptions().SetMaxRollupLevels(3))
   959  	require.NoError(t, validator.ValidateSnapshot(view))
   960  }
   961  
   962  func TestValidatorValidateRollupRuleRollupOpDuplicateRollupTag(t *testing.T) {
   963  	rr1, err := pipeline.NewRollupOp(
   964  		pipeline.GroupByRollupType,
   965  		"rName1",
   966  		[]string{"rtagName1", "rtagName2", "rtagName2"},
   967  		aggregation.DefaultID,
   968  	)
   969  	require.NoError(t, err)
   970  
   971  	view := view.RuleSet{
   972  		RollupRules: []view.RollupRule{
   973  			{
   974  				Name:   "snapshot1",
   975  				Filter: testTypeTag + ":" + testCounterType,
   976  				Targets: []view.RollupTarget{
   977  					{
   978  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   979  							{
   980  								Type:   pipeline.RollupOpType,
   981  								Rollup: rr1,
   982  							},
   983  						}),
   984  						StoragePolicies: testStoragePolicies(),
   985  					},
   986  				},
   987  			},
   988  		},
   989  	}
   990  	validator := NewValidator(testValidatorOptions())
   991  	err = validator.ValidateSnapshot(view)
   992  	require.Error(t, err)
   993  	require.True(t, strings.Contains(err.Error(), "duplicate rollup tag: 'rtagName2'"))
   994  }
   995  
   996  func TestValidatorValidateRollupRuleRollupOpMissingRequiredTag(t *testing.T) {
   997  	rr1, err := pipeline.NewRollupOp(
   998  		pipeline.GroupByRollupType,
   999  		"rName1",
  1000  		[]string{"rtagName1", "rtagName2"},
  1001  		aggregation.DefaultID,
  1002  	)
  1003  	require.NoError(t, err)
  1004  
  1005  	view := view.RuleSet{
  1006  		RollupRules: []view.RollupRule{
  1007  			{
  1008  				Name:   "snapshot1",
  1009  				Filter: testTypeTag + ":" + testCounterType,
  1010  				Targets: []view.RollupTarget{
  1011  					{
  1012  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
  1013  							{
  1014  								Type:   pipeline.RollupOpType,
  1015  								Rollup: rr1,
  1016  							},
  1017  						}),
  1018  						StoragePolicies: testStoragePolicies(),
  1019  					},
  1020  				},
  1021  			},
  1022  		},
  1023  	}
  1024  	validator := NewValidator(testValidatorOptions().SetRequiredRollupTags([]string{"requiredTag"}))
  1025  	err = validator.ValidateSnapshot(view)
  1026  	require.Error(t, err)
  1027  	require.True(t, strings.Contains(err.Error(), "missing required rollup tag: 'requiredTag'"))
  1028  }
  1029  
  1030  func TestValidatorValidateRollupRuleRollupOpWithInvalidMetricName(t *testing.T) {
  1031  	rr1, err := pipeline.NewRollupOp(
  1032  		pipeline.GroupByRollupType,
  1033  		"rName$1",
  1034  		[]string{"rtagName1", "rtagName2"},
  1035  		aggregation.DefaultID,
  1036  	)
  1037  	require.NoError(t, err)
  1038  	invalidChars := []rune{'$'}
  1039  	view := view.RuleSet{
  1040  		RollupRules: []view.RollupRule{
  1041  			{
  1042  				Name:   "snapshot1",
  1043  				Filter: testTypeTag + ":" + testCounterType,
  1044  				Targets: []view.RollupTarget{
  1045  					{
  1046  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
  1047  							{
  1048  								Type:   pipeline.RollupOpType,
  1049  								Rollup: rr1,
  1050  							},
  1051  						}),
  1052  						StoragePolicies: testStoragePolicies(),
  1053  					},
  1054  				},
  1055  			},
  1056  		},
  1057  	}
  1058  
  1059  	validator := NewValidator(testValidatorOptions().SetMetricNameInvalidChars(invalidChars))
  1060  	require.Error(t, validator.ValidateSnapshot(view))
  1061  }
  1062  
  1063  func TestValidatorValidateRollupRuleRollupOpWithEmptyMetricName(t *testing.T) {
  1064  	rr1, err := pipeline.NewRollupOp(
  1065  		pipeline.GroupByRollupType,
  1066  		"",
  1067  		[]string{"rtagName1", "rtagName2"},
  1068  		aggregation.DefaultID,
  1069  	)
  1070  	require.NoError(t, err)
  1071  	invalidChars := []rune{'$'}
  1072  	view := view.RuleSet{
  1073  		RollupRules: []view.RollupRule{
  1074  			{
  1075  				Name:   "snapshot1",
  1076  				Filter: testTypeTag + ":" + testCounterType,
  1077  				Targets: []view.RollupTarget{
  1078  					{
  1079  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
  1080  							{
  1081  								Type:   pipeline.RollupOpType,
  1082  								Rollup: rr1,
  1083  							},
  1084  						}),
  1085  						StoragePolicies: testStoragePolicies(),
  1086  					},
  1087  				},
  1088  			},
  1089  		},
  1090  	}
  1091  
  1092  	validator := NewValidator(testValidatorOptions().SetMetricNameInvalidChars(invalidChars))
  1093  	require.Error(t, validator.ValidateSnapshot(view))
  1094  }
  1095  
  1096  func TestValidatorValidateRollupRuleRollupOpWithValidMetricName(t *testing.T) {
  1097  	rr1, err := pipeline.NewRollupOp(
  1098  		pipeline.GroupByRollupType,
  1099  		"",
  1100  		[]string{"rtagName1", "rtagName2"},
  1101  		aggregation.DefaultID,
  1102  	)
  1103  	require.NoError(t, err)
  1104  	invalidChars := []rune{' ', '%'}
  1105  	view := view.RuleSet{
  1106  		RollupRules: []view.RollupRule{
  1107  			{
  1108  				Name:   "snapshot1",
  1109  				Filter: testTypeTag + ":" + testCounterType,
  1110  				Targets: []view.RollupTarget{
  1111  					{
  1112  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
  1113  							{
  1114  								Type:   pipeline.RollupOpType,
  1115  								Rollup: rr1,
  1116  							},
  1117  						}),
  1118  						StoragePolicies: testStoragePolicies(),
  1119  					},
  1120  				},
  1121  			},
  1122  		},
  1123  	}
  1124  
  1125  	validator := NewValidator(testValidatorOptions().SetMetricNameInvalidChars(invalidChars))
  1126  	require.Error(t, validator.ValidateSnapshot(view))
  1127  }
  1128  
  1129  func TestValidatorValidateRollupRuleRollupOpWithInvalidTagName(t *testing.T) {
  1130  	rr1, err := pipeline.NewRollupOp(
  1131  		pipeline.GroupByRollupType,
  1132  		"foo",
  1133  		[]string{"rtagName1", "rtagName2", "$"},
  1134  		aggregation.DefaultID,
  1135  	)
  1136  	require.NoError(t, err)
  1137  	invalidChars := []rune{'$'}
  1138  	view := view.RuleSet{
  1139  		RollupRules: []view.RollupRule{
  1140  			{
  1141  				Name:   "snapshot1",
  1142  				Filter: testTypeTag + ":" + testCounterType,
  1143  				Targets: []view.RollupTarget{
  1144  					{
  1145  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
  1146  							{
  1147  								Type:   pipeline.RollupOpType,
  1148  								Rollup: rr1,
  1149  							},
  1150  						}),
  1151  						StoragePolicies: testStoragePolicies(),
  1152  					},
  1153  				},
  1154  			},
  1155  		},
  1156  	}
  1157  
  1158  	validator := NewValidator(testValidatorOptions().SetTagNameInvalidChars(invalidChars))
  1159  	require.Error(t, validator.ValidateSnapshot(view))
  1160  }
  1161  
  1162  func TestValidatorValidateRollupRuleRollupOpWithValidTagName(t *testing.T) {
  1163  	rr1, err := pipeline.NewRollupOp(
  1164  		pipeline.GroupByRollupType,
  1165  		"foo",
  1166  		[]string{"rtagName1", "rtagName2", "$"},
  1167  		aggregation.DefaultID,
  1168  	)
  1169  	require.NoError(t, err)
  1170  	invalidChars := []rune{' ', '%'}
  1171  	view := view.RuleSet{
  1172  		RollupRules: []view.RollupRule{
  1173  			{
  1174  				Name:   "snapshot1",
  1175  				Filter: testTypeTag + ":" + testCounterType,
  1176  				Targets: []view.RollupTarget{
  1177  					{
  1178  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
  1179  							{
  1180  								Type:   pipeline.RollupOpType,
  1181  								Rollup: rr1,
  1182  							},
  1183  						}),
  1184  						StoragePolicies: testStoragePolicies(),
  1185  					},
  1186  				},
  1187  			},
  1188  		},
  1189  	}
  1190  
  1191  	validator := NewValidator(testValidatorOptions().SetMetricNameInvalidChars(invalidChars))
  1192  	require.NoError(t, validator.ValidateSnapshot(view))
  1193  }
  1194  
  1195  func TestValidatorValidateNoTimertypeFilter(t *testing.T) {
  1196  	for _, test := range []string{
  1197  		"rollup",
  1198  		"mapping",
  1199  	} {
  1200  		t.Run(test, func(t *testing.T) {
  1201  			ruleView := view.RuleSet{}
  1202  			if test == "rollup" {
  1203  				ruleView.RollupRules = []view.RollupRule{
  1204  					{
  1205  						Name:   "foo",
  1206  						Filter: "service:bar timertype:count",
  1207  					},
  1208  				}
  1209  			} else {
  1210  				ruleView.MappingRules = []view.MappingRule{
  1211  					{
  1212  						Name:       "foo",
  1213  						Filter:     "service:bar timertype:count",
  1214  						DropPolicy: policy.DropMust,
  1215  					},
  1216  				}
  1217  			}
  1218  
  1219  			validator := NewValidator(testValidatorOptions().SetFilterInvalidTagNames([]string{"timertype"}))
  1220  			assert.Error(t, validator.ValidateSnapshot(ruleView))
  1221  
  1222  			if test == "rollup" {
  1223  				ruleView.RollupRules = ruleView.RollupRules[:0]
  1224  			} else {
  1225  				ruleView.MappingRules = ruleView.MappingRules[:0]
  1226  			}
  1227  
  1228  			assert.NoError(t, validator.ValidateSnapshot(ruleView))
  1229  		})
  1230  	}
  1231  }
  1232  
  1233  func TestValidatorValidateRollupRuleRollupOpMultipleAggregationTypes(t *testing.T) {
  1234  	rr1, err := pipeline.NewRollupOp(
  1235  		pipeline.GroupByRollupType,
  1236  		"rName1",
  1237  		[]string{"rtagName1", "rtagName2"},
  1238  		aggregation.MustCompressTypes(aggregation.Count, aggregation.Max),
  1239  	)
  1240  	require.NoError(t, err)
  1241  
  1242  	testAggregationTypes := []aggregation.Type{aggregation.Count, aggregation.Max}
  1243  	view := view.RuleSet{
  1244  		RollupRules: []view.RollupRule{
  1245  			{
  1246  				Name:   "snapshot1",
  1247  				Filter: testTypeTag + ":" + testTimerType,
  1248  				Targets: []view.RollupTarget{
  1249  					{
  1250  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
  1251  							{
  1252  								Type:   pipeline.RollupOpType,
  1253  								Rollup: rr1,
  1254  							},
  1255  						}),
  1256  						StoragePolicies: policy.StoragePolicies{
  1257  							policy.MustParseStoragePolicy("10s:6h"),
  1258  						},
  1259  					},
  1260  				},
  1261  			},
  1262  		},
  1263  	}
  1264  	inputs := []struct {
  1265  		opts      Options
  1266  		expectErr bool
  1267  	}{
  1268  		{
  1269  			// By default multiple aggregation types are allowed for timers.
  1270  			opts:      testValidatorOptions().SetDefaultAllowedFirstLevelAggregationTypes(testAggregationTypes),
  1271  			expectErr: false,
  1272  		},
  1273  		{
  1274  			// Explicitly disallow multiple aggregation types for timers.
  1275  			opts:      testValidatorOptions().SetDefaultAllowedFirstLevelAggregationTypes(testAggregationTypes).SetMultiAggregationTypesEnabledFor(nil),
  1276  			expectErr: true,
  1277  		},
  1278  	}
  1279  
  1280  	for _, input := range inputs {
  1281  		validator := NewValidator(input.opts)
  1282  		if input.expectErr {
  1283  			require.Error(t, validator.ValidateSnapshot(view))
  1284  		} else {
  1285  			require.NoError(t, validator.ValidateSnapshot(view))
  1286  		}
  1287  	}
  1288  }
  1289  
  1290  func TestValidatorValidateRollupRuleRollupOpFirstLevelAggregationTypes(t *testing.T) {
  1291  	rr1, err := pipeline.NewRollupOp(
  1292  		pipeline.GroupByRollupType,
  1293  		"rName1",
  1294  		[]string{"rtagName1", "rtagName2"},
  1295  		aggregation.MustCompressTypes(aggregation.Count, aggregation.Max),
  1296  	)
  1297  	require.NoError(t, err)
  1298  	testAggregationTypes := []aggregation.Type{aggregation.Count, aggregation.Max}
  1299  	view := view.RuleSet{
  1300  		RollupRules: []view.RollupRule{
  1301  			{
  1302  				Name:   "snapshot1",
  1303  				Filter: testTypeTag + ":" + testTimerType,
  1304  				Targets: []view.RollupTarget{
  1305  					{
  1306  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
  1307  							{
  1308  								Type:   pipeline.RollupOpType,
  1309  								Rollup: rr1,
  1310  							},
  1311  						}),
  1312  						StoragePolicies: policy.StoragePolicies{
  1313  							policy.MustParseStoragePolicy("10s:6h"),
  1314  						},
  1315  					},
  1316  				},
  1317  			},
  1318  		},
  1319  	}
  1320  	inputs := []struct {
  1321  		opts      Options
  1322  		expectErr bool
  1323  	}{
  1324  		{
  1325  			// By default no custom aggregation type is allowed.
  1326  			opts:      testValidatorOptions(),
  1327  			expectErr: true,
  1328  		},
  1329  		{
  1330  			// Aggregation type is allowed through the default list of custom aggregation types.
  1331  			opts:      testValidatorOptions().SetDefaultAllowedFirstLevelAggregationTypes(testAggregationTypes),
  1332  			expectErr: false,
  1333  		},
  1334  		{
  1335  			// Aggregation type is allowed through the list of custom aggregation types for timers.
  1336  			opts:      testValidatorOptions().SetAllowedFirstLevelAggregationTypesFor(metric.TimerType, testAggregationTypes),
  1337  			expectErr: false,
  1338  		},
  1339  	}
  1340  
  1341  	for _, input := range inputs {
  1342  		validator := NewValidator(input.opts)
  1343  		if input.expectErr {
  1344  			require.Error(t, validator.ValidateSnapshot(view))
  1345  		} else {
  1346  			require.NoError(t, validator.ValidateSnapshot(view))
  1347  		}
  1348  	}
  1349  }
  1350  
  1351  func TestValidatorValidateRollupRuleRollupOpNonFirstLevelAggregationTypes(t *testing.T) {
  1352  	rr1, err := pipeline.NewRollupOp(
  1353  		pipeline.GroupByRollupType,
  1354  		"rName1",
  1355  		[]string{"rtagName1", "rtagName2"},
  1356  		aggregation.MustCompressTypes(aggregation.Count, aggregation.Max),
  1357  	)
  1358  	require.NoError(t, err)
  1359  	testAggregationTypes := []aggregation.Type{aggregation.Count, aggregation.Max}
  1360  	view := view.RuleSet{
  1361  		RollupRules: []view.RollupRule{
  1362  			{
  1363  				Name:   "snapshot1",
  1364  				Filter: testTypeTag + ":" + testTimerType,
  1365  				Targets: []view.RollupTarget{
  1366  					{
  1367  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
  1368  							{
  1369  								Type:           pipeline.TransformationOpType,
  1370  								Transformation: pipeline.TransformationOp{Type: transformation.PerSecond},
  1371  							},
  1372  							{
  1373  								Type:   pipeline.RollupOpType,
  1374  								Rollup: rr1,
  1375  							},
  1376  						}),
  1377  						StoragePolicies: policy.StoragePolicies{
  1378  							policy.MustParseStoragePolicy("10s:6h"),
  1379  						},
  1380  					},
  1381  				},
  1382  			},
  1383  		},
  1384  	}
  1385  	inputs := []struct {
  1386  		opts      Options
  1387  		expectErr bool
  1388  	}{
  1389  		{
  1390  			// By default no custom aggregation type is allowed.
  1391  			opts:      testValidatorOptions(),
  1392  			expectErr: true,
  1393  		},
  1394  		{
  1395  			// Aggregation type is allowed through the default list of custom aggregation types.
  1396  			opts:      testValidatorOptions().SetDefaultAllowedNonFirstLevelAggregationTypes(testAggregationTypes),
  1397  			expectErr: false,
  1398  		},
  1399  		{
  1400  			// Aggregation type is allowed through the list of non-first-level aggregation types for timers.
  1401  			opts:      testValidatorOptions().SetAllowedNonFirstLevelAggregationTypesFor(metric.TimerType, testAggregationTypes),
  1402  			expectErr: false,
  1403  		},
  1404  	}
  1405  
  1406  	for _, input := range inputs {
  1407  		validator := NewValidator(input.opts)
  1408  		if input.expectErr {
  1409  			require.Error(t, validator.ValidateSnapshot(view))
  1410  		} else {
  1411  			require.NoError(t, validator.ValidateSnapshot(view))
  1412  		}
  1413  	}
  1414  }
  1415  
  1416  func TestValidatorValidateRollupRuleRollupTargetWithStoragePolicies(t *testing.T) {
  1417  	rr1, err := pipeline.NewRollupOp(
  1418  		pipeline.GroupByRollupType,
  1419  		"rName1",
  1420  		[]string{"rtagName1", "rtagName2"},
  1421  		aggregation.DefaultID,
  1422  	)
  1423  	require.NoError(t, err)
  1424  	storagePolicies := testStoragePolicies()
  1425  	view := view.RuleSet{
  1426  		RollupRules: []view.RollupRule{
  1427  			{
  1428  				Name:   "snapshot1",
  1429  				Filter: testTypeTag + ":" + testTimerType,
  1430  				Targets: []view.RollupTarget{
  1431  					{
  1432  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
  1433  							{
  1434  								Type:   pipeline.RollupOpType,
  1435  								Rollup: rr1,
  1436  							},
  1437  						}),
  1438  						StoragePolicies: storagePolicies,
  1439  					},
  1440  				},
  1441  			},
  1442  		},
  1443  	}
  1444  
  1445  	inputs := []struct {
  1446  		opts      Options
  1447  		expectErr bool
  1448  	}{
  1449  		{
  1450  			// By default policy is allowed.
  1451  			opts:      testValidatorOptions().SetDefaultAllowedStoragePolicies(policy.StoragePolicies{}),
  1452  			expectErr: true,
  1453  		},
  1454  		{
  1455  			// Policy is allowed through the default list of policies.
  1456  			opts:      testValidatorOptions().SetDefaultAllowedStoragePolicies(storagePolicies),
  1457  			expectErr: false,
  1458  		},
  1459  		{
  1460  			// Policy is allowed through the list of policies allowed for timers.
  1461  			opts:      testValidatorOptions().SetAllowedStoragePoliciesFor(metric.TimerType, storagePolicies),
  1462  			expectErr: false,
  1463  		},
  1464  	}
  1465  
  1466  	for _, input := range inputs {
  1467  		validator := NewValidator(input.opts)
  1468  		if input.expectErr {
  1469  			require.Error(t, validator.ValidateSnapshot(view))
  1470  		} else {
  1471  			require.NoError(t, validator.ValidateSnapshot(view))
  1472  		}
  1473  	}
  1474  }
  1475  
  1476  func TestValidatorValidateRollupRuleRollupTargetWithNoStoragePolicies(t *testing.T) {
  1477  	rr1, err := pipeline.NewRollupOp(
  1478  		pipeline.GroupByRollupType,
  1479  		"rName1",
  1480  		[]string{"rtagName1", "rtagName2"},
  1481  		aggregation.DefaultID,
  1482  	)
  1483  	require.NoError(t, err)
  1484  	view := view.RuleSet{
  1485  		RollupRules: []view.RollupRule{
  1486  			{
  1487  				Name:   "snapshot1",
  1488  				Filter: testTypeTag + ":" + testTimerType,
  1489  				Targets: []view.RollupTarget{
  1490  					{
  1491  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
  1492  							{
  1493  								Type:   pipeline.RollupOpType,
  1494  								Rollup: rr1,
  1495  							},
  1496  						}),
  1497  					},
  1498  				},
  1499  			},
  1500  		},
  1501  	}
  1502  	validator := NewValidator(testValidatorOptions())
  1503  	err = validator.ValidateSnapshot(view)
  1504  	require.Error(t, err)
  1505  	require.True(t, strings.Contains(err.Error(), "no storage policies"))
  1506  }
  1507  
  1508  func TestValidatorValidateRollupRuleRollupOpWithDuplicateStoragePolicies(t *testing.T) {
  1509  	rr1, err := pipeline.NewRollupOp(
  1510  		pipeline.GroupByRollupType,
  1511  		"rName1",
  1512  		[]string{"rtagName1", "rtagName2"},
  1513  		aggregation.DefaultID,
  1514  	)
  1515  	require.NoError(t, err)
  1516  	view := view.RuleSet{
  1517  		RollupRules: []view.RollupRule{
  1518  			{
  1519  				Name:   "snapshot1",
  1520  				Filter: testTypeTag + ":" + testTimerType,
  1521  				Targets: []view.RollupTarget{
  1522  					{
  1523  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
  1524  							{
  1525  								Type:   pipeline.RollupOpType,
  1526  								Rollup: rr1,
  1527  							},
  1528  						}),
  1529  						StoragePolicies: policy.StoragePolicies{
  1530  							policy.MustParseStoragePolicy("10s:6h"),
  1531  							policy.MustParseStoragePolicy("10s:6h"),
  1532  						},
  1533  					},
  1534  				},
  1535  			},
  1536  		},
  1537  	}
  1538  
  1539  	validator := NewValidator(testValidatorOptions())
  1540  	err = validator.ValidateSnapshot(view)
  1541  	require.Error(t, err)
  1542  	require.True(t, strings.Contains(err.Error(), "duplicate storage policy '10s:6h'"))
  1543  }
  1544  
  1545  func TestValidatorValidateRollupRuleDisallowedStoragePolicies(t *testing.T) {
  1546  	rr1, err := pipeline.NewRollupOp(
  1547  		pipeline.GroupByRollupType,
  1548  		"rName1",
  1549  		[]string{"rtagName1", "rtagName2"},
  1550  		aggregation.DefaultID,
  1551  	)
  1552  	require.NoError(t, err)
  1553  	view := view.RuleSet{
  1554  		RollupRules: []view.RollupRule{
  1555  			{
  1556  				Name:   "snapshot1",
  1557  				Filter: testTypeTag + ":" + testTimerType,
  1558  				Targets: []view.RollupTarget{
  1559  					{
  1560  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
  1561  							{
  1562  								Type:   pipeline.RollupOpType,
  1563  								Rollup: rr1,
  1564  							},
  1565  						}),
  1566  						StoragePolicies: policy.StoragePolicies{
  1567  							policy.MustParseStoragePolicy("1s:6h"),
  1568  						},
  1569  					},
  1570  				},
  1571  			},
  1572  		},
  1573  	}
  1574  
  1575  	validator := NewValidator(testValidatorOptions())
  1576  	require.Error(t, validator.ValidateSnapshot(view))
  1577  }
  1578  
  1579  func TestValidatorRollupRule(t *testing.T) {
  1580  	rr1, err := pipeline.NewRollupOp(
  1581  		pipeline.GroupByRollupType,
  1582  		"rName1",
  1583  		[]string{"rtagName1", "rtagName2"},
  1584  		aggregation.MustCompressTypes(aggregation.Sum),
  1585  	)
  1586  	require.NoError(t, err)
  1587  	view := view.RuleSet{
  1588  		RollupRules: []view.RollupRule{
  1589  			{
  1590  				Name:   "snapshot1",
  1591  				Filter: testTypeTag + ":" + testGaugeType,
  1592  				Targets: []view.RollupTarget{
  1593  					{
  1594  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
  1595  							{
  1596  								Type:        pipeline.AggregationOpType,
  1597  								Aggregation: pipeline.AggregationOp{Type: aggregation.Last},
  1598  							},
  1599  							{
  1600  								Type:           pipeline.TransformationOpType,
  1601  								Transformation: pipeline.TransformationOp{Type: transformation.PerSecond},
  1602  							},
  1603  							{
  1604  								Type:   pipeline.RollupOpType,
  1605  								Rollup: rr1,
  1606  							},
  1607  						}),
  1608  						StoragePolicies: testStoragePolicies(),
  1609  					},
  1610  				},
  1611  			},
  1612  		},
  1613  	}
  1614  
  1615  	firstLevelAggregationTypes := aggregation.Types{aggregation.Last}
  1616  	nonFirstLevelAggregationTypes := aggregation.Types{aggregation.Sum}
  1617  	opts := testValidatorOptions().
  1618  		SetAllowedFirstLevelAggregationTypesFor(metric.GaugeType, firstLevelAggregationTypes).
  1619  		SetAllowedNonFirstLevelAggregationTypesFor(metric.GaugeType, nonFirstLevelAggregationTypes)
  1620  	validator := NewValidator(opts)
  1621  	require.NoError(t, validator.ValidateSnapshot(view))
  1622  }
  1623  
  1624  func TestValidatorValidateRollupRuleDuplicateRollupIDs(t *testing.T) {
  1625  	rr1, err := pipeline.NewRollupOp(
  1626  		pipeline.GroupByRollupType,
  1627  		"rName1",
  1628  		[]string{"rtagName1", "rtagName2"},
  1629  		aggregation.MustCompressTypes(aggregation.Sum),
  1630  	)
  1631  	require.NoError(t, err)
  1632  	rr2, err := pipeline.NewRollupOp(
  1633  		pipeline.GroupByRollupType,
  1634  		"rName1",
  1635  		[]string{"rtagName1", "rtagName2"},
  1636  		aggregation.DefaultID,
  1637  	)
  1638  	require.NoError(t, err)
  1639  	view := view.RuleSet{
  1640  		RollupRules: []view.RollupRule{
  1641  			{
  1642  				Name:   "snapshot1",
  1643  				Filter: testTypeTag + ":" + testGaugeType,
  1644  				Targets: []view.RollupTarget{
  1645  					{
  1646  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
  1647  							{
  1648  								Type:        pipeline.AggregationOpType,
  1649  								Aggregation: pipeline.AggregationOp{Type: aggregation.Last},
  1650  							},
  1651  							{
  1652  								Type:           pipeline.TransformationOpType,
  1653  								Transformation: pipeline.TransformationOp{Type: transformation.PerSecond},
  1654  							},
  1655  							{
  1656  								Type:   pipeline.RollupOpType,
  1657  								Rollup: rr1,
  1658  							},
  1659  						}),
  1660  						StoragePolicies: testStoragePolicies(),
  1661  					},
  1662  				},
  1663  			},
  1664  			{
  1665  				Name:   "snapshot2",
  1666  				Filter: testTypeTag + ":" + testGaugeType,
  1667  				Targets: []view.RollupTarget{
  1668  					{
  1669  						Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
  1670  							{
  1671  								Type:   pipeline.RollupOpType,
  1672  								Rollup: rr2,
  1673  							},
  1674  						}),
  1675  						StoragePolicies: testStoragePolicies(),
  1676  					},
  1677  				},
  1678  			},
  1679  		},
  1680  	}
  1681  	firstLevelAggregationTypes := aggregation.Types{aggregation.Last}
  1682  	nonFirstLevelAggregationTypes := aggregation.Types{aggregation.Sum}
  1683  	opts := testValidatorOptions().
  1684  		SetAllowedFirstLevelAggregationTypesFor(metric.GaugeType, firstLevelAggregationTypes).
  1685  		SetAllowedNonFirstLevelAggregationTypesFor(metric.GaugeType, nonFirstLevelAggregationTypes)
  1686  	validator := NewValidator(opts)
  1687  	err = validator.ValidateSnapshot(view)
  1688  	require.Error(t, err)
  1689  	require.True(t, strings.Contains(err.Error(), "more than one rollup operations with name 'rName1' and tags '[rtagName1 rtagName2]' exist"))
  1690  	_, ok := err.(errors.InvalidInputError)
  1691  	require.True(t, ok)
  1692  }
  1693  
  1694  func TestValidatorValidateMappingRuleValidDropPolicy(t *testing.T) {
  1695  	view := view.RuleSet{
  1696  		MappingRules: []view.MappingRule{
  1697  			{
  1698  				Name:       "snapshot1",
  1699  				Filter:     "tag1:value1",
  1700  				DropPolicy: policy.DropMust,
  1701  			},
  1702  		},
  1703  	}
  1704  	validator := NewValidator(testValidatorOptions())
  1705  	require.NoError(t, validator.ValidateSnapshot(view))
  1706  }
  1707  
  1708  func TestValidatorValidateMappingRuleInvalidDropPolicy(t *testing.T) {
  1709  	type invalidDropPolicyTest struct {
  1710  		name string
  1711  		view view.RuleSet
  1712  	}
  1713  
  1714  	tests := []invalidDropPolicyTest{
  1715  		{
  1716  			name: "invalid drop policy",
  1717  			view: view.RuleSet{
  1718  				MappingRules: []view.MappingRule{
  1719  					{
  1720  						Name:       "snapshot1",
  1721  						Filter:     "tag1:value1",
  1722  						DropPolicy: policy.DropPolicy(math.MaxUint32),
  1723  					},
  1724  				},
  1725  			},
  1726  		},
  1727  	}
  1728  
  1729  	for _, dropPolicy := range policy.ValidDropPolicies() {
  1730  		if dropPolicy == policy.DropNone {
  1731  			continue // The drop none policy is always valid, since its not active
  1732  		}
  1733  
  1734  		tests = append(tests, []invalidDropPolicyTest{
  1735  			{
  1736  				name: dropPolicy.String() + " policy with storage policies",
  1737  				view: view.RuleSet{
  1738  					MappingRules: []view.MappingRule{
  1739  						{
  1740  							Name:            "snapshot1",
  1741  							Filter:          "tag1:value1",
  1742  							DropPolicy:      policy.DropMust,
  1743  							StoragePolicies: testStoragePolicies(),
  1744  						},
  1745  					},
  1746  				},
  1747  			},
  1748  			{
  1749  				name: dropPolicy.String() + " policy with non-default aggregation ID",
  1750  				view: view.RuleSet{
  1751  					MappingRules: []view.MappingRule{
  1752  						{
  1753  							Name:       "snapshot1",
  1754  							Filter:     "tag1:value1",
  1755  							DropPolicy: policy.DropMust,
  1756  							AggregationID: aggregation.NewIDCompressor().MustCompress(
  1757  								aggregation.Types{aggregation.Last},
  1758  							),
  1759  						},
  1760  					},
  1761  				},
  1762  			},
  1763  		}...)
  1764  	}
  1765  
  1766  	validator := NewValidator(testValidatorOptions())
  1767  
  1768  	for _, test := range tests {
  1769  		t.Run(test.name, func(t *testing.T) {
  1770  			require.Error(t, validator.ValidateSnapshot(test.view))
  1771  		})
  1772  	}
  1773  }
  1774  
  1775  func testKVNamespaceValidator(t *testing.T) namespace.Validator {
  1776  	store := mem.NewStore()
  1777  	_, err := store.Set(testNamespacesKey, &commonpb.StringArrayProto{Values: testNamespaces})
  1778  	require.NoError(t, err)
  1779  	kvOpts := kv.NewNamespaceValidatorOptions().
  1780  		SetKVStore(store).
  1781  		SetValidNamespacesKey(testNamespacesKey)
  1782  	nsValidator, err := kv.NewNamespaceValidator(kvOpts)
  1783  	require.NoError(t, err)
  1784  	return nsValidator
  1785  }
  1786  
  1787  func testMetricTypesFn() MetricTypesFn {
  1788  	return func(filters filters.TagFilterValueMap) ([]metric.Type, error) {
  1789  		fv, exists := filters[testTypeTag]
  1790  		if !exists {
  1791  			return []metric.Type{metric.UnknownType}, nil
  1792  		}
  1793  		switch fv.Pattern {
  1794  		case testCounterType:
  1795  			return []metric.Type{metric.CounterType}, nil
  1796  		case testTimerType:
  1797  			return []metric.Type{metric.TimerType}, nil
  1798  		case testGaugeType:
  1799  			return []metric.Type{metric.GaugeType}, nil
  1800  		default:
  1801  			return nil, fmt.Errorf("unknown metric type %v", fv.Pattern)
  1802  		}
  1803  	}
  1804  }
  1805  
  1806  func testStoragePolicies() policy.StoragePolicies {
  1807  	return policy.StoragePolicies{
  1808  		policy.MustParseStoragePolicy("10s:6h"),
  1809  		policy.MustParseStoragePolicy("1m:24h"),
  1810  	}
  1811  }
  1812  
  1813  func testValidatorOptions() Options {
  1814  	return NewOptions().
  1815  		SetDefaultAllowedStoragePolicies(testStoragePolicies()).
  1816  		SetDefaultAllowedFirstLevelAggregationTypes(nil).
  1817  		SetMetricTypesFn(testMetricTypesFn()).
  1818  		SetMultiAggregationTypesEnabledFor([]metric.Type{metric.TimerType})
  1819  }