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

     1  // Copyright (c) 2020 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 rules
    22  
    23  import (
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/m3db/m3/src/metrics/aggregation"
    29  	"github.com/m3db/m3/src/metrics/errors"
    30  	"github.com/m3db/m3/src/metrics/filters"
    31  	"github.com/m3db/m3/src/metrics/generated/proto/aggregationpb"
    32  	"github.com/m3db/m3/src/metrics/generated/proto/metricpb"
    33  	"github.com/m3db/m3/src/metrics/generated/proto/pipelinepb"
    34  	"github.com/m3db/m3/src/metrics/generated/proto/policypb"
    35  	"github.com/m3db/m3/src/metrics/generated/proto/rulepb"
    36  	"github.com/m3db/m3/src/metrics/generated/proto/transformationpb"
    37  	"github.com/m3db/m3/src/metrics/pipeline"
    38  	"github.com/m3db/m3/src/metrics/policy"
    39  	"github.com/m3db/m3/src/metrics/rules/view"
    40  	"github.com/m3db/m3/src/metrics/transformation"
    41  	"github.com/m3db/m3/src/query/models"
    42  	xtime "github.com/m3db/m3/src/x/time"
    43  
    44  	"github.com/google/go-cmp/cmp"
    45  	"github.com/google/go-cmp/cmp/cmpopts"
    46  	"github.com/stretchr/testify/require"
    47  )
    48  
    49  var (
    50  	testRollupRuleSnapshot1V1Proto = &rulepb.RollupRuleSnapshot{
    51  		Name:               "foo",
    52  		Tombstoned:         false,
    53  		CutoverNanos:       12345000000,
    54  		LastUpdatedAtNanos: 12345000000,
    55  		LastUpdatedBy:      "someone",
    56  		Filter:             "tag1:value1 tag2:value2",
    57  		KeepOriginal:       false,
    58  		Tags:               []*metricpb.Tag{},
    59  		Targets: []*rulepb.RollupTarget{
    60  			{
    61  				Name: "rName1",
    62  				Tags: []string{"rtagName1", "rtagName2"},
    63  				Policies: []*policypb.Policy{
    64  					{
    65  						StoragePolicy: &policypb.StoragePolicy{
    66  							Resolution: policypb.Resolution{
    67  								WindowSize: int64(10 * time.Second),
    68  								Precision:  int64(time.Second),
    69  							},
    70  							Retention: policypb.Retention{
    71  								Period: int64(24 * time.Hour),
    72  							},
    73  						},
    74  					},
    75  				},
    76  			},
    77  		},
    78  	}
    79  	testRollupRuleSnapshot2V1Proto = &rulepb.RollupRuleSnapshot{
    80  		Name:               "bar",
    81  		Tombstoned:         true,
    82  		CutoverNanos:       67890000000,
    83  		LastUpdatedAtNanos: 67890000000,
    84  		LastUpdatedBy:      "someone-else",
    85  		Filter:             "tag3:value3 tag4:value4",
    86  		KeepOriginal:       false,
    87  		Tags:               []*metricpb.Tag{},
    88  		Targets: []*rulepb.RollupTarget{
    89  			{
    90  				Name: "rName1",
    91  				Tags: []string{"rtagName1", "rtagName2"},
    92  				Policies: []*policypb.Policy{
    93  					{
    94  						StoragePolicy: &policypb.StoragePolicy{
    95  							Resolution: policypb.Resolution{
    96  								WindowSize: int64(time.Minute),
    97  								Precision:  int64(time.Minute),
    98  							},
    99  							Retention: policypb.Retention{
   100  								Period: int64(24 * time.Hour),
   101  							},
   102  						},
   103  						AggregationTypes: []aggregationpb.AggregationType{
   104  							aggregationpb.AggregationType_MEAN,
   105  						},
   106  					},
   107  					{
   108  						StoragePolicy: &policypb.StoragePolicy{
   109  							Resolution: policypb.Resolution{
   110  								WindowSize: int64(5 * time.Minute),
   111  								Precision:  int64(time.Minute),
   112  							},
   113  							Retention: policypb.Retention{
   114  								Period: int64(48 * time.Hour),
   115  							},
   116  						},
   117  						AggregationTypes: []aggregationpb.AggregationType{
   118  							aggregationpb.AggregationType_MEAN,
   119  						},
   120  					},
   121  				},
   122  			},
   123  		},
   124  	}
   125  	testRollupRuleSnapshot3V2Proto = &rulepb.RollupRuleSnapshot{
   126  		Name:               "foo",
   127  		Tombstoned:         false,
   128  		CutoverNanos:       12345000000,
   129  		LastUpdatedAtNanos: 12345000000,
   130  		LastUpdatedBy:      "someone",
   131  		Filter:             "tag1:value1 tag2:value2",
   132  		KeepOriginal:       false,
   133  		Tags:               []*metricpb.Tag{},
   134  		TargetsV2: []*rulepb.RollupTargetV2{
   135  			{
   136  				Pipeline: &pipelinepb.Pipeline{
   137  					Ops: []pipelinepb.PipelineOp{
   138  						{
   139  							Type: pipelinepb.PipelineOp_AGGREGATION,
   140  							Aggregation: &pipelinepb.AggregationOp{
   141  								Type: aggregationpb.AggregationType_SUM,
   142  							},
   143  						},
   144  						{
   145  							Type: pipelinepb.PipelineOp_TRANSFORMATION,
   146  							Transformation: &pipelinepb.TransformationOp{
   147  								Type: transformationpb.TransformationType_ABSOLUTE,
   148  							},
   149  						},
   150  						{
   151  							Type: pipelinepb.PipelineOp_ROLLUP,
   152  							Rollup: &pipelinepb.RollupOp{
   153  								NewName: "testRollupOp",
   154  								Tags:    []string{"testTag1", "testTag2"},
   155  								AggregationTypes: []aggregationpb.AggregationType{
   156  									aggregationpb.AggregationType_MIN,
   157  									aggregationpb.AggregationType_MAX,
   158  								},
   159  							},
   160  						},
   161  					},
   162  				},
   163  				StoragePolicies: []*policypb.StoragePolicy{
   164  					{
   165  						Resolution: policypb.Resolution{
   166  							WindowSize: 10 * time.Second.Nanoseconds(),
   167  							Precision:  time.Second.Nanoseconds(),
   168  						},
   169  						Retention: policypb.Retention{
   170  							Period: 24 * time.Hour.Nanoseconds(),
   171  						},
   172  					},
   173  					{
   174  						Resolution: policypb.Resolution{
   175  							WindowSize: time.Minute.Nanoseconds(),
   176  							Precision:  time.Minute.Nanoseconds(),
   177  						},
   178  						Retention: policypb.Retention{
   179  							Period: 720 * time.Hour.Nanoseconds(),
   180  						},
   181  					},
   182  					{
   183  						Resolution: policypb.Resolution{
   184  							WindowSize: time.Hour.Nanoseconds(),
   185  							Precision:  time.Hour.Nanoseconds(),
   186  						},
   187  						Retention: policypb.Retention{
   188  							Period: 365 * 24 * time.Hour.Nanoseconds(),
   189  						},
   190  					},
   191  				},
   192  			},
   193  			{
   194  				Pipeline: &pipelinepb.Pipeline{
   195  					Ops: []pipelinepb.PipelineOp{
   196  						{
   197  							Type: pipelinepb.PipelineOp_TRANSFORMATION,
   198  							Transformation: &pipelinepb.TransformationOp{
   199  								Type: transformationpb.TransformationType_PERSECOND,
   200  							},
   201  						},
   202  						{
   203  							Type: pipelinepb.PipelineOp_ROLLUP,
   204  							Rollup: &pipelinepb.RollupOp{
   205  								NewName: "testRollupOp2",
   206  								Tags:    []string{"testTag3", "testTag4"},
   207  							},
   208  						},
   209  					},
   210  				},
   211  				StoragePolicies: []*policypb.StoragePolicy{
   212  					{
   213  						Resolution: policypb.Resolution{
   214  							WindowSize: time.Minute.Nanoseconds(),
   215  							Precision:  time.Minute.Nanoseconds(),
   216  						},
   217  						Retention: policypb.Retention{
   218  							Period: 720 * time.Hour.Nanoseconds(),
   219  						},
   220  					},
   221  				},
   222  			},
   223  		},
   224  	}
   225  	testRollupRuleSnapshot4V2Proto = &rulepb.RollupRuleSnapshot{
   226  		Name:               "bar",
   227  		Tombstoned:         true,
   228  		CutoverNanos:       67890000000,
   229  		LastUpdatedAtNanos: 67890000000,
   230  		LastUpdatedBy:      "someone-else",
   231  		Filter:             "tag3:value3 tag4:value4",
   232  		KeepOriginal:       true,
   233  		Tags:               []*metricpb.Tag{},
   234  		TargetsV2: []*rulepb.RollupTargetV2{
   235  			{
   236  				Pipeline: &pipelinepb.Pipeline{
   237  					Ops: []pipelinepb.PipelineOp{
   238  						{
   239  							Type: pipelinepb.PipelineOp_ROLLUP,
   240  							Rollup: &pipelinepb.RollupOp{
   241  								NewName: "testRollupOp2",
   242  								Tags:    []string{"testTag3", "testTag4"},
   243  								AggregationTypes: []aggregationpb.AggregationType{
   244  									aggregationpb.AggregationType_LAST,
   245  								},
   246  							},
   247  						},
   248  					},
   249  				},
   250  				StoragePolicies: []*policypb.StoragePolicy{
   251  					{
   252  						Resolution: policypb.Resolution{
   253  							WindowSize: 10 * time.Minute.Nanoseconds(),
   254  							Precision:  time.Minute.Nanoseconds(),
   255  						},
   256  						Retention: policypb.Retention{
   257  							Period: 1800 * time.Hour.Nanoseconds(),
   258  						},
   259  					},
   260  				},
   261  			},
   262  		},
   263  	}
   264  	testRollupRule1V1Proto = &rulepb.RollupRule{
   265  		Uuid: "12669817-13ae-40e6-ba2f-33087b262c68",
   266  		Snapshots: []*rulepb.RollupRuleSnapshot{
   267  			testRollupRuleSnapshot1V1Proto,
   268  			testRollupRuleSnapshot2V1Proto,
   269  		},
   270  	}
   271  	testRollupRule2V2Proto = &rulepb.RollupRule{
   272  		Uuid: "12669817-13ae-40e6-ba2f-33087b262c68",
   273  		Snapshots: []*rulepb.RollupRuleSnapshot{
   274  			testRollupRuleSnapshot3V2Proto,
   275  			testRollupRuleSnapshot4V2Proto,
   276  		},
   277  	}
   278  	rr1, rr1err = pipeline.NewRollupOp(
   279  		pipeline.GroupByRollupType,
   280  		"rName1",
   281  		[]string{"rtagName1", "rtagName2"},
   282  		aggregation.DefaultID,
   283  	)
   284  	rr2, rr2err = pipeline.NewRollupOp(
   285  		pipeline.GroupByRollupType,
   286  		"rName1",
   287  		[]string{"rtagName1", "rtagName2"},
   288  		aggregation.MustCompressTypes(aggregation.Mean),
   289  	)
   290  	rr3, rr3err = pipeline.NewRollupOp(
   291  		pipeline.GroupByRollupType,
   292  		"testRollupOp",
   293  		[]string{"testTag1", "testTag2"},
   294  		aggregation.MustCompressTypes(aggregation.Min, aggregation.Max),
   295  	)
   296  	rr4, rr4err = pipeline.NewRollupOp(
   297  		pipeline.GroupByRollupType,
   298  		"testRollupOp2",
   299  		[]string{"testTag3", "testTag4"},
   300  		aggregation.DefaultID,
   301  	)
   302  	rr5, rr5err = pipeline.NewRollupOp(
   303  		pipeline.GroupByRollupType,
   304  		"testRollupOp2",
   305  		[]string{"testTag3", "testTag4"},
   306  		aggregation.MustCompressTypes(aggregation.Last),
   307  	)
   308  	testRollupRuleSnapshot1 = &rollupRuleSnapshot{
   309  		name:         "foo",
   310  		tombstoned:   false,
   311  		cutoverNanos: 12345000000,
   312  		rawFilter:    "tag1:value1 tag2:value2",
   313  		keepOriginal: false,
   314  		tags:         []models.Tag{},
   315  		targets: []rollupTarget{
   316  			{
   317  				Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   318  					{
   319  						Type:   pipeline.RollupOpType,
   320  						Rollup: rr1,
   321  					},
   322  				}),
   323  				StoragePolicies: policy.StoragePolicies{
   324  					policy.NewStoragePolicy(10*time.Second, xtime.Second, 24*time.Hour),
   325  				},
   326  			},
   327  		},
   328  		lastUpdatedAtNanos: 12345000000,
   329  		lastUpdatedBy:      "someone",
   330  	}
   331  	testRollupRuleSnapshot2 = &rollupRuleSnapshot{
   332  		name:         "bar",
   333  		tombstoned:   true,
   334  		cutoverNanos: 67890000000,
   335  		rawFilter:    "tag3:value3 tag4:value4",
   336  		keepOriginal: false,
   337  		tags:         []models.Tag{},
   338  		targets: []rollupTarget{
   339  			{
   340  				Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   341  					{
   342  						Type:   pipeline.RollupOpType,
   343  						Rollup: rr2,
   344  					},
   345  				}),
   346  				StoragePolicies: policy.StoragePolicies{
   347  					policy.NewStoragePolicy(time.Minute, xtime.Minute, 24*time.Hour),
   348  					policy.NewStoragePolicy(5*time.Minute, xtime.Minute, 48*time.Hour),
   349  				},
   350  			},
   351  		},
   352  		lastUpdatedAtNanos: 67890000000,
   353  		lastUpdatedBy:      "someone-else",
   354  	}
   355  	testRollupRuleSnapshot3 = &rollupRuleSnapshot{
   356  		name:         "foo",
   357  		tombstoned:   false,
   358  		cutoverNanos: 12345000000,
   359  		rawFilter:    "tag1:value1 tag2:value2",
   360  		keepOriginal: false,
   361  		tags:         []models.Tag{},
   362  		targets: []rollupTarget{
   363  			{
   364  				Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   365  					{
   366  						Type: pipeline.AggregationOpType,
   367  						Aggregation: pipeline.AggregationOp{
   368  							Type: aggregation.Sum,
   369  						},
   370  					},
   371  					{
   372  						Type: pipeline.TransformationOpType,
   373  						Transformation: pipeline.TransformationOp{
   374  							Type: transformation.Absolute,
   375  						},
   376  					},
   377  					{
   378  						Type:   pipeline.RollupOpType,
   379  						Rollup: rr3,
   380  					},
   381  				}),
   382  				StoragePolicies: policy.StoragePolicies{
   383  					policy.NewStoragePolicy(10*time.Second, xtime.Second, 24*time.Hour),
   384  					policy.NewStoragePolicy(time.Minute, xtime.Minute, 720*time.Hour),
   385  					policy.NewStoragePolicy(time.Hour, xtime.Hour, 365*24*time.Hour),
   386  				},
   387  			},
   388  			{
   389  				Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   390  					{
   391  						Type: pipeline.TransformationOpType,
   392  						Transformation: pipeline.TransformationOp{
   393  							Type: transformation.PerSecond,
   394  						},
   395  					},
   396  					{
   397  						Type:   pipeline.RollupOpType,
   398  						Rollup: rr4,
   399  					},
   400  				}),
   401  				StoragePolicies: policy.StoragePolicies{
   402  					policy.NewStoragePolicy(time.Minute, xtime.Minute, 720*time.Hour),
   403  				},
   404  			},
   405  		},
   406  		lastUpdatedAtNanos: 12345000000,
   407  		lastUpdatedBy:      "someone",
   408  	}
   409  	testRollupRuleSnapshot4 = &rollupRuleSnapshot{
   410  		name:         "bar",
   411  		tombstoned:   true,
   412  		cutoverNanos: 67890000000,
   413  		rawFilter:    "tag3:value3 tag4:value4",
   414  		keepOriginal: true,
   415  		tags:         []models.Tag{},
   416  		targets: []rollupTarget{
   417  			{
   418  				Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   419  					{
   420  						Type:   pipeline.RollupOpType,
   421  						Rollup: rr5,
   422  					},
   423  				}),
   424  				StoragePolicies: policy.StoragePolicies{
   425  					policy.NewStoragePolicy(10*time.Minute, xtime.Minute, 1800*time.Hour),
   426  				},
   427  			},
   428  		},
   429  		lastUpdatedAtNanos: 67890000000,
   430  		lastUpdatedBy:      "someone-else",
   431  	}
   432  	testRollupRule1 = &rollupRule{
   433  		uuid: "12669817-13ae-40e6-ba2f-33087b262c68",
   434  		snapshots: []*rollupRuleSnapshot{
   435  			testRollupRuleSnapshot1,
   436  			testRollupRuleSnapshot2,
   437  		},
   438  	}
   439  	testRollupRule2 = &rollupRule{
   440  		uuid: "12669817-13ae-40e6-ba2f-33087b262c68",
   441  		snapshots: []*rollupRuleSnapshot{
   442  			testRollupRuleSnapshot3,
   443  			testRollupRuleSnapshot4,
   444  		},
   445  	}
   446  	testRollupRuleSnapshotCmpOpts = []cmp.Option{
   447  		cmp.AllowUnexported(rollupRuleSnapshot{}),
   448  		cmpopts.IgnoreInterfaces(struct{ filters.TagsFilter }{}),
   449  	}
   450  	testRollupRuleCmpOpts = []cmp.Option{
   451  		cmp.AllowUnexported(rollupRule{}),
   452  		cmp.AllowUnexported(rollupRuleSnapshot{}),
   453  		cmpopts.IgnoreInterfaces(struct{ filters.TagsFilter }{}),
   454  	}
   455  )
   456  
   457  func TestErrCheck(t *testing.T) {
   458  	require.NoError(t, rr1err)
   459  	require.NoError(t, rr2err)
   460  	require.NoError(t, rr3err)
   461  	require.NoError(t, rr4err)
   462  	require.NoError(t, rr5err)
   463  }
   464  
   465  func TestNewRollupRuleSnapshotFromProtoNilProto(t *testing.T) {
   466  	_, err := newRollupRuleSnapshotFromProto(nil, testTagsFilterOptions())
   467  	require.Equal(t, errNilRollupRuleSnapshotProto, err)
   468  }
   469  
   470  func TestNewRollupRuleSnapshotFromV1ProtoInvalidProto(t *testing.T) {
   471  	proto := &rulepb.RollupRuleSnapshot{
   472  		Targets: []*rulepb.RollupTarget{
   473  			{
   474  				Name: "rName1",
   475  				Tags: []string{"rtagName1", "rtagName2"},
   476  				Policies: []*policypb.Policy{
   477  					{},
   478  				},
   479  			},
   480  		},
   481  	}
   482  	_, err := newRollupRuleSnapshotFromProto(proto, testTagsFilterOptions())
   483  	require.Error(t, err)
   484  }
   485  
   486  func TestNewRollupRuleSnapshotFromV1Proto(t *testing.T) {
   487  	filterOpts := testTagsFilterOptions()
   488  	inputs := []*rulepb.RollupRuleSnapshot{
   489  		testRollupRuleSnapshot1V1Proto,
   490  		testRollupRuleSnapshot2V1Proto,
   491  	}
   492  	expected := []*rollupRuleSnapshot{
   493  		testRollupRuleSnapshot1,
   494  		testRollupRuleSnapshot2,
   495  	}
   496  	for i, input := range inputs {
   497  		res, err := newRollupRuleSnapshotFromProto(input, filterOpts)
   498  		require.NoError(t, err)
   499  		require.True(t, cmp.Equal(expected[i], res, testRollupRuleSnapshotCmpOpts...))
   500  		require.NotNil(t, res.filter)
   501  	}
   502  }
   503  
   504  func TestNewRollupRuleSnapshotFromV2ProtoInvalidProto(t *testing.T) {
   505  	filterOpts := testTagsFilterOptions()
   506  	proto := &rulepb.RollupRuleSnapshot{
   507  		TargetsV2: []*rulepb.RollupTargetV2{
   508  			{
   509  				Pipeline: &pipelinepb.Pipeline{
   510  					Ops: []pipelinepb.PipelineOp{
   511  						{
   512  							Type: pipelinepb.PipelineOp_TRANSFORMATION,
   513  							Transformation: &pipelinepb.TransformationOp{
   514  								Type: transformationpb.TransformationType_UNKNOWN,
   515  							},
   516  						},
   517  					},
   518  				},
   519  				StoragePolicies: []*policypb.StoragePolicy{
   520  					{
   521  						Resolution: policypb.Resolution{
   522  							WindowSize: 10 * time.Minute.Nanoseconds(),
   523  							Precision:  time.Minute.Nanoseconds(),
   524  						},
   525  						Retention: policypb.Retention{
   526  							Period: 1800 * time.Hour.Nanoseconds(),
   527  						},
   528  					},
   529  				},
   530  			},
   531  		},
   532  	}
   533  	_, err := newRollupRuleSnapshotFromProto(proto, filterOpts)
   534  	require.Error(t, err)
   535  }
   536  
   537  func TestNewRollupRuleSnapshotFromV2Proto(t *testing.T) {
   538  	filterOpts := testTagsFilterOptions()
   539  	inputs := []*rulepb.RollupRuleSnapshot{
   540  		testRollupRuleSnapshot3V2Proto,
   541  		testRollupRuleSnapshot4V2Proto,
   542  	}
   543  	expected := []*rollupRuleSnapshot{
   544  		testRollupRuleSnapshot3,
   545  		testRollupRuleSnapshot4,
   546  	}
   547  	for i, input := range inputs {
   548  		res, err := newRollupRuleSnapshotFromProto(input, filterOpts)
   549  		require.NoError(t, err)
   550  		require.True(t, cmp.Equal(expected[i], res, testRollupRuleSnapshotCmpOpts...))
   551  		require.NotNil(t, res.filter)
   552  	}
   553  }
   554  
   555  func TestNewRollupRuleSnapshotFromProtoTombstoned(t *testing.T) {
   556  	filterOpts := testTagsFilterOptions()
   557  	input := &rulepb.RollupRuleSnapshot{
   558  		Name:               "foo",
   559  		Tombstoned:         true,
   560  		CutoverNanos:       12345000000,
   561  		LastUpdatedAtNanos: 12345000000,
   562  		LastUpdatedBy:      "someone",
   563  		Filter:             "tag1:value1 tag2:value2",
   564  		KeepOriginal:       false,
   565  	}
   566  	res, err := newRollupRuleSnapshotFromProto(input, filterOpts)
   567  	require.NoError(t, err)
   568  
   569  	expected := &rollupRuleSnapshot{
   570  		name:               "foo",
   571  		tombstoned:         true,
   572  		cutoverNanos:       12345000000,
   573  		rawFilter:          "tag1:value1 tag2:value2",
   574  		lastUpdatedAtNanos: 12345000000,
   575  		lastUpdatedBy:      "someone",
   576  		keepOriginal:       false,
   577  		tags:               []models.Tag{},
   578  	}
   579  	require.True(t, cmp.Equal(expected, res, testRollupRuleSnapshotCmpOpts...))
   580  	require.NotNil(t, res.filter)
   581  }
   582  
   583  func TestNewRollupRuleSnapshotNoRollupTargets(t *testing.T) {
   584  	proto := &rulepb.RollupRuleSnapshot{}
   585  	_, err := newRollupRuleSnapshotFromProto(proto, testTagsFilterOptions())
   586  	require.Equal(t, errNoRollupTargetsInRollupRuleSnapshot, err)
   587  }
   588  
   589  func TestNewRollupRuleSnapshotFromFields(t *testing.T) {
   590  	res, err := newRollupRuleSnapshotFromFields(
   591  		testRollupRuleSnapshot3.name,
   592  		testRollupRuleSnapshot3.cutoverNanos,
   593  		testRollupRuleSnapshot3.rawFilter,
   594  		testRollupRuleSnapshot3.targets,
   595  		testRollupRuleSnapshot3.filter,
   596  		testRollupRuleSnapshot3.lastUpdatedAtNanos,
   597  		testRollupRuleSnapshot3.lastUpdatedBy,
   598  		false,
   599  		[]models.Tag{},
   600  	)
   601  	require.NoError(t, err)
   602  	require.True(t, cmp.Equal(testRollupRuleSnapshot3, res, testRollupRuleSnapshotCmpOpts...))
   603  }
   604  
   605  func TestNewRollupRuleSnapshotFromFieldsValidationError(t *testing.T) {
   606  	badFilters := []string{
   607  		"tag3:",
   608  		"tag3:*a*b*c*d",
   609  		"ab[cd",
   610  	}
   611  
   612  	for _, f := range badFilters {
   613  		_, err := newRollupRuleSnapshotFromFields(
   614  			"bar",
   615  			12345000000,
   616  			f,
   617  			nil,
   618  			nil,
   619  			1234,
   620  			"test_user",
   621  			false,
   622  			nil,
   623  		)
   624  		require.Error(t, err)
   625  		_, ok := err.(errors.ValidationError)
   626  		require.True(t, ok)
   627  	}
   628  }
   629  
   630  func TestRollupRuleSnapshotProto(t *testing.T) {
   631  	snapshots := []*rollupRuleSnapshot{
   632  		testRollupRuleSnapshot3,
   633  		testRollupRuleSnapshot4,
   634  	}
   635  	expected := []*rulepb.RollupRuleSnapshot{
   636  		testRollupRuleSnapshot3V2Proto,
   637  		testRollupRuleSnapshot4V2Proto,
   638  	}
   639  	for i, snapshot := range snapshots {
   640  		proto, err := snapshot.proto()
   641  		require.NoError(t, err)
   642  		require.Equal(t, expected[i], proto)
   643  	}
   644  }
   645  
   646  func TestNewRollupRuleFromProtoNilProto(t *testing.T) {
   647  	_, err := newRollupRuleFromProto(nil, testTagsFilterOptions())
   648  	require.Equal(t, errNilRollupRuleProto, err)
   649  }
   650  
   651  func TestNewRollupRuleFromProtoValidProto(t *testing.T) {
   652  	filterOpts := testTagsFilterOptions()
   653  	inputs := []*rulepb.RollupRule{
   654  		testRollupRule1V1Proto,
   655  		testRollupRule2V2Proto,
   656  	}
   657  	expected := []*rollupRule{
   658  		testRollupRule1,
   659  		testRollupRule2,
   660  	}
   661  	for i, input := range inputs {
   662  		res, err := newRollupRuleFromProto(input, filterOpts)
   663  		require.NoError(t, err)
   664  		require.True(t, cmp.Equal(expected[i], res, testRollupRuleCmpOpts...))
   665  	}
   666  }
   667  
   668  func TestRollupRuleClone(t *testing.T) {
   669  	inputs := []*rollupRule{
   670  		testRollupRule1,
   671  		testRollupRule2,
   672  	}
   673  	for _, input := range inputs {
   674  		cloned := input.clone()
   675  		require.True(t, cmp.Equal(&cloned, input, testRollupRuleCmpOpts...))
   676  
   677  		// Asserting that modifying the clone doesn't modify the original rollup rule.
   678  		cloned2 := input.clone()
   679  		require.True(t, cmp.Equal(&cloned2, input, testRollupRuleCmpOpts...))
   680  		cloned2.snapshots[0].tombstoned = true
   681  		require.False(t, cmp.Equal(&cloned2, input, testRollupRuleCmpOpts...))
   682  		require.True(t, cmp.Equal(&cloned, input, testRollupRuleCmpOpts...))
   683  	}
   684  }
   685  
   686  func TestRollupRuleProto(t *testing.T) {
   687  	inputs := []*rollupRule{
   688  		testRollupRule2,
   689  	}
   690  	expected := []*rulepb.RollupRule{
   691  		testRollupRule2V2Proto,
   692  	}
   693  	for i, input := range inputs {
   694  		res, err := input.proto()
   695  		require.NoError(t, err)
   696  		require.Equal(t, expected[i], res)
   697  	}
   698  }
   699  
   700  func TestRollupRuleActiveSnapshotNotFound(t *testing.T) {
   701  	require.Nil(t, testRollupRule2.activeSnapshot(0))
   702  }
   703  
   704  func TestRollupRuleActiveSnapshotFound(t *testing.T) {
   705  	require.Equal(t, testRollupRule2.snapshots[1], testRollupRule2.activeSnapshot(100000000000))
   706  }
   707  
   708  func TestRollupRuleActiveRuleNotFound(t *testing.T) {
   709  	require.Equal(t, testRollupRule2, testRollupRule2.activeRule(0))
   710  }
   711  
   712  func TestRollupRuleActiveRuleFound(t *testing.T) {
   713  	expected := &rollupRule{
   714  		uuid:      testRollupRule2.uuid,
   715  		snapshots: testRollupRule2.snapshots[1:],
   716  	}
   717  	require.Equal(t, expected, testRollupRule2.activeRule(100000000000))
   718  }
   719  
   720  func TestRollupNameNoSnapshot(t *testing.T) {
   721  	rr := rollupRule{
   722  		uuid:      "blah",
   723  		snapshots: []*rollupRuleSnapshot{},
   724  	}
   725  	_, err := rr.name()
   726  	require.Equal(t, errNoRuleSnapshots, err)
   727  }
   728  
   729  func TestRollupTombstonedNoSnapshot(t *testing.T) {
   730  	rr := rollupRule{
   731  		uuid:      "blah",
   732  		snapshots: []*rollupRuleSnapshot{},
   733  	}
   734  	require.True(t, rr.tombstoned())
   735  }
   736  
   737  func TestRollupTombstoned(t *testing.T) {
   738  	require.True(t, testRollupRule2.tombstoned())
   739  }
   740  
   741  func TestRollupRuleMarkTombstoned(t *testing.T) {
   742  	proto := &rulepb.RollupRule{
   743  		Uuid: "12669817-13ae-40e6-ba2f-33087b262c68",
   744  		Snapshots: []*rulepb.RollupRuleSnapshot{
   745  			testRollupRuleSnapshot3V2Proto,
   746  		},
   747  	}
   748  	rr, err := newRollupRuleFromProto(proto, testTagsFilterOptions())
   749  	require.NoError(t, err)
   750  
   751  	meta := UpdateMetadata{
   752  		cutoverNanos:   67890000000,
   753  		updatedAtNanos: 10000,
   754  		updatedBy:      "john",
   755  	}
   756  	require.NoError(t, rr.markTombstoned(meta))
   757  	require.Equal(t, 2, len(rr.snapshots))
   758  	require.True(t, cmp.Equal(testRollupRuleSnapshot3, rr.snapshots[0], testRollupRuleSnapshotCmpOpts...))
   759  
   760  	expected := &rollupRuleSnapshot{
   761  		name:               "foo",
   762  		tombstoned:         true,
   763  		cutoverNanos:       67890000000,
   764  		rawFilter:          "tag1:value1 tag2:value2",
   765  		lastUpdatedAtNanos: 10000,
   766  		lastUpdatedBy:      "john",
   767  		keepOriginal:       false,
   768  		tags:               []models.Tag{},
   769  	}
   770  	require.True(t, cmp.Equal(expected, rr.snapshots[1], testRollupRuleSnapshotCmpOpts...))
   771  }
   772  
   773  func TestRollupRuleMarkTombstonedNoSnapshots(t *testing.T) {
   774  	rr := &rollupRule{}
   775  	require.Error(t, rr.markTombstoned(UpdateMetadata{}))
   776  }
   777  
   778  func TestRollupRuleMarkTombstonedAlreadyTombstoned(t *testing.T) {
   779  	err := testRollupRule2.markTombstoned(UpdateMetadata{})
   780  	require.Error(t, err)
   781  	require.True(t, strings.Contains(err.Error(), "bar is already tombstoned"))
   782  }
   783  
   784  func TestRollupRuleRollupRuleView(t *testing.T) {
   785  	res, err := testRollupRule2.rollupRuleView(1)
   786  	require.NoError(t, err)
   787  	rr1, err = pipeline.NewRollupOp(
   788  		pipeline.GroupByRollupType,
   789  		"testRollupOp2",
   790  		[]string{"testTag3", "testTag4"},
   791  		aggregation.MustCompressTypes(aggregation.Last),
   792  	)
   793  	require.NoError(t, err)
   794  	expected := view.RollupRule{
   795  		ID:            "12669817-13ae-40e6-ba2f-33087b262c68",
   796  		Name:          "bar",
   797  		Tombstoned:    true,
   798  		CutoverMillis: 67890,
   799  		Filter:        "tag3:value3 tag4:value4",
   800  		KeepOriginal:  true,
   801  		Targets: []view.RollupTarget{
   802  			{
   803  				Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   804  					{
   805  						Type:   pipeline.RollupOpType,
   806  						Rollup: rr1,
   807  					},
   808  				}),
   809  				StoragePolicies: policy.StoragePolicies{
   810  					policy.NewStoragePolicy(10*time.Minute, xtime.Minute, 1800*time.Hour),
   811  				},
   812  			},
   813  		},
   814  		LastUpdatedAtMillis: 67890,
   815  		LastUpdatedBy:       "someone-else",
   816  		Tags:                []models.Tag{},
   817  	}
   818  	require.Equal(t, expected, res)
   819  }
   820  
   821  func TestNewRollupRuleViewError(t *testing.T) {
   822  	badIndices := []int{-2, 2, 30}
   823  	for _, i := range badIndices {
   824  		_, err := testRollupRule2.rollupRuleView(i)
   825  		require.Equal(t, errRollupRuleSnapshotIndexOutOfRange, err)
   826  	}
   827  }
   828  
   829  func TestNewRollupRuleHistory(t *testing.T) {
   830  	history, err := testRollupRule2.history()
   831  	require.NoError(t, err)
   832  
   833  	rr1, err = pipeline.NewRollupOp(
   834  		pipeline.GroupByRollupType,
   835  		"testRollupOp2",
   836  		[]string{"testTag3", "testTag4"},
   837  		aggregation.MustCompressTypes(aggregation.Last),
   838  	)
   839  	require.NoError(t, err)
   840  	rr2, err = pipeline.NewRollupOp(
   841  		pipeline.GroupByRollupType,
   842  		"testRollupOp",
   843  		[]string{"testTag1", "testTag2"},
   844  		aggregation.MustCompressTypes(aggregation.Min, aggregation.Max),
   845  	)
   846  	require.NoError(t, err)
   847  	rr3, err = pipeline.NewRollupOp(
   848  		pipeline.GroupByRollupType,
   849  		"testRollupOp2",
   850  		[]string{"testTag3", "testTag4"},
   851  		aggregation.DefaultID,
   852  	)
   853  	require.NoError(t, err)
   854  
   855  	expected := []view.RollupRule{
   856  		{
   857  			ID:            "12669817-13ae-40e6-ba2f-33087b262c68",
   858  			Name:          "bar",
   859  			Tombstoned:    true,
   860  			CutoverMillis: 67890,
   861  			Filter:        "tag3:value3 tag4:value4",
   862  			KeepOriginal:  true,
   863  			Targets: []view.RollupTarget{
   864  				{
   865  					Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   866  						{
   867  							Type:   pipeline.RollupOpType,
   868  							Rollup: rr1,
   869  						},
   870  					}),
   871  					StoragePolicies: policy.StoragePolicies{
   872  						policy.NewStoragePolicy(10*time.Minute, xtime.Minute, 1800*time.Hour),
   873  					},
   874  				},
   875  			},
   876  			LastUpdatedAtMillis: 67890,
   877  			LastUpdatedBy:       "someone-else",
   878  			Tags:                []models.Tag{},
   879  		},
   880  		{
   881  			ID:            "12669817-13ae-40e6-ba2f-33087b262c68",
   882  			Name:          "foo",
   883  			Tombstoned:    false,
   884  			CutoverMillis: 12345,
   885  			Filter:        "tag1:value1 tag2:value2",
   886  			Targets: []view.RollupTarget{
   887  				{
   888  					Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   889  						{
   890  							Type: pipeline.AggregationOpType,
   891  							Aggregation: pipeline.AggregationOp{
   892  								Type: aggregation.Sum,
   893  							},
   894  						},
   895  						{
   896  							Type: pipeline.TransformationOpType,
   897  							Transformation: pipeline.TransformationOp{
   898  								Type: transformation.Absolute,
   899  							},
   900  						},
   901  						{
   902  							Type:   pipeline.RollupOpType,
   903  							Rollup: rr2,
   904  						},
   905  					}),
   906  					StoragePolicies: policy.StoragePolicies{
   907  						policy.NewStoragePolicy(10*time.Second, xtime.Second, 24*time.Hour),
   908  						policy.NewStoragePolicy(time.Minute, xtime.Minute, 720*time.Hour),
   909  						policy.NewStoragePolicy(time.Hour, xtime.Hour, 365*24*time.Hour),
   910  					},
   911  				},
   912  				{
   913  					Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
   914  						{
   915  							Type: pipeline.TransformationOpType,
   916  							Transformation: pipeline.TransformationOp{
   917  								Type: transformation.PerSecond,
   918  							},
   919  						},
   920  						{
   921  							Type:   pipeline.RollupOpType,
   922  							Rollup: rr3,
   923  						},
   924  					}),
   925  					StoragePolicies: policy.StoragePolicies{
   926  						policy.NewStoragePolicy(time.Minute, xtime.Minute, 720*time.Hour),
   927  					},
   928  				},
   929  			},
   930  			LastUpdatedAtMillis: 12345,
   931  			LastUpdatedBy:       "someone",
   932  			Tags:                []models.Tag{},
   933  		},
   934  	}
   935  	require.Equal(t, expected, history)
   936  }