github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/metrics/rules/store/kv/store_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 kv
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/m3db/m3/src/cluster/kv/mem"
    30  	merrors "github.com/m3db/m3/src/metrics/errors"
    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/rules"
    37  	"github.com/m3db/m3/src/metrics/rules/view"
    38  
    39  	"github.com/stretchr/testify/require"
    40  )
    41  
    42  const (
    43  	testNamespaceKey  = "testKey"
    44  	testNamespace     = "fooNs"
    45  	testRuleSetKeyFmt = "rules/%s"
    46  )
    47  
    48  var (
    49  	testNamespaces = &rulepb.Namespaces{
    50  		Namespaces: []*rulepb.Namespace{
    51  			&rulepb.Namespace{
    52  				Name: "fooNs",
    53  				Snapshots: []*rulepb.NamespaceSnapshot{
    54  					&rulepb.NamespaceSnapshot{
    55  						ForRulesetVersion: 1,
    56  						Tombstoned:        false,
    57  					},
    58  					&rulepb.NamespaceSnapshot{
    59  						ForRulesetVersion: 2,
    60  						Tombstoned:        false,
    61  					},
    62  				},
    63  			},
    64  			&rulepb.Namespace{
    65  				Name: "barNs",
    66  				Snapshots: []*rulepb.NamespaceSnapshot{
    67  					&rulepb.NamespaceSnapshot{
    68  						ForRulesetVersion: 1,
    69  						Tombstoned:        false,
    70  					},
    71  					&rulepb.NamespaceSnapshot{
    72  						ForRulesetVersion: 2,
    73  						Tombstoned:        true,
    74  					},
    75  				},
    76  			},
    77  		},
    78  	}
    79  
    80  	testRuleSetKey = fmt.Sprintf(testRuleSetKeyFmt, testNamespace)
    81  	testRuleSet    = &rulepb.RuleSet{
    82  		Uuid:               "ruleset",
    83  		Namespace:          "fooNs",
    84  		CreatedAtNanos:     1234,
    85  		LastUpdatedAtNanos: 5678,
    86  		Tombstoned:         false,
    87  		CutoverNanos:       34923,
    88  		MappingRules: []*rulepb.MappingRule{
    89  			&rulepb.MappingRule{
    90  				Uuid: "12669817-13ae-40e6-ba2f-33087b262c68",
    91  				Snapshots: []*rulepb.MappingRuleSnapshot{
    92  					&rulepb.MappingRuleSnapshot{
    93  						Name:         "foo",
    94  						Tombstoned:   false,
    95  						CutoverNanos: 12345,
    96  						Filter:       "tag1:value1 tag2:value2",
    97  						StoragePolicies: []*policypb.StoragePolicy{
    98  							&policypb.StoragePolicy{
    99  								Resolution: policypb.Resolution{
   100  									WindowSize: int64(10 * time.Second),
   101  									Precision:  int64(time.Second),
   102  								},
   103  								Retention: policypb.Retention{
   104  									Period: int64(24 * time.Hour),
   105  								},
   106  							},
   107  						},
   108  						Tags: []*metricpb.Tag{
   109  							{
   110  								Name:  []byte("name"),
   111  								Value: []byte("name"),
   112  							},
   113  						},
   114  					},
   115  					&rulepb.MappingRuleSnapshot{
   116  						Name:         "foo",
   117  						Tombstoned:   false,
   118  						CutoverNanos: 67890,
   119  						Filter:       "tag3:value3 tag4:value4",
   120  						StoragePolicies: []*policypb.StoragePolicy{
   121  							&policypb.StoragePolicy{
   122  								Resolution: policypb.Resolution{
   123  									WindowSize: int64(time.Minute),
   124  									Precision:  int64(time.Minute),
   125  								},
   126  								Retention: policypb.Retention{
   127  									Period: int64(24 * time.Hour),
   128  								},
   129  							},
   130  							&policypb.StoragePolicy{
   131  								Resolution: policypb.Resolution{
   132  									WindowSize: int64(5 * time.Minute),
   133  									Precision:  int64(time.Minute),
   134  								},
   135  								Retention: policypb.Retention{
   136  									Period: int64(48 * time.Hour),
   137  								},
   138  							},
   139  						},
   140  						Tags: []*metricpb.Tag{
   141  							{
   142  								Name:  []byte("name"),
   143  								Value: []byte("name"),
   144  							},
   145  						},
   146  					},
   147  				},
   148  			},
   149  			&rulepb.MappingRule{
   150  				Uuid: "12669817-13ae-40e6-ba2f-33087b262c68",
   151  				Snapshots: []*rulepb.MappingRuleSnapshot{
   152  					&rulepb.MappingRuleSnapshot{
   153  						Name:         "dup",
   154  						Tombstoned:   false,
   155  						CutoverNanos: 12345,
   156  						Filter:       "tag1:value1 tag2:value2",
   157  						AggregationTypes: []aggregationpb.AggregationType{
   158  							aggregationpb.AggregationType_P999,
   159  						},
   160  						StoragePolicies: []*policypb.StoragePolicy{
   161  							&policypb.StoragePolicy{
   162  								Resolution: policypb.Resolution{
   163  									WindowSize: int64(10 * time.Second),
   164  									Precision:  int64(time.Second),
   165  								},
   166  								Retention: policypb.Retention{
   167  									Period: int64(24 * time.Hour),
   168  								},
   169  							},
   170  						},
   171  						Tags: []*metricpb.Tag{
   172  							{
   173  								Name:  []byte("name"),
   174  								Value: []byte("name"),
   175  							},
   176  						},
   177  					},
   178  				},
   179  			},
   180  		},
   181  		RollupRules: []*rulepb.RollupRule{
   182  			&rulepb.RollupRule{
   183  				Uuid: "12669817-13ae-40e6-ba2f-33087b262c68",
   184  				Snapshots: []*rulepb.RollupRuleSnapshot{
   185  					&rulepb.RollupRuleSnapshot{
   186  						Name:         "foo2",
   187  						Tombstoned:   false,
   188  						CutoverNanos: 12345,
   189  						Filter:       "tag1:value1 tag2:value2",
   190  						Tags: []*metricpb.Tag{
   191  							{
   192  								Name:  []byte("name"),
   193  								Value: []byte("name"),
   194  							},
   195  						},
   196  						TargetsV2: []*rulepb.RollupTargetV2{
   197  							&rulepb.RollupTargetV2{
   198  								Pipeline: &pipelinepb.Pipeline{
   199  									Ops: []pipelinepb.PipelineOp{
   200  										{
   201  											Type: pipelinepb.PipelineOp_ROLLUP,
   202  											Rollup: &pipelinepb.RollupOp{
   203  												NewName: "rName1",
   204  												Tags:    []string{"rtagName1", "rtagName2"},
   205  											},
   206  										},
   207  									},
   208  								},
   209  								StoragePolicies: []*policypb.StoragePolicy{
   210  									&policypb.StoragePolicy{
   211  										Resolution: policypb.Resolution{
   212  											WindowSize: int64(10 * time.Second),
   213  											Precision:  int64(time.Second),
   214  										},
   215  										Retention: policypb.Retention{
   216  											Period: int64(24 * time.Hour),
   217  										},
   218  									},
   219  								},
   220  							},
   221  						},
   222  					},
   223  					&rulepb.RollupRuleSnapshot{
   224  						Name:         "bar",
   225  						Tombstoned:   true,
   226  						CutoverNanos: 67890,
   227  						Filter:       "tag3:value3 tag4:value4",
   228  						Tags: []*metricpb.Tag{
   229  							{
   230  								Name:  []byte("name"),
   231  								Value: []byte("name"),
   232  							},
   233  						},
   234  						TargetsV2: []*rulepb.RollupTargetV2{
   235  							&rulepb.RollupTargetV2{
   236  								Pipeline: &pipelinepb.Pipeline{
   237  									Ops: []pipelinepb.PipelineOp{
   238  										{
   239  											Type: pipelinepb.PipelineOp_ROLLUP,
   240  											Rollup: &pipelinepb.RollupOp{
   241  												NewName: "rName1",
   242  												Tags:    []string{"rtagName1", "rtagName2"},
   243  												AggregationTypes: []aggregationpb.AggregationType{
   244  													aggregationpb.AggregationType_MEAN,
   245  												},
   246  											},
   247  										},
   248  									},
   249  								},
   250  								StoragePolicies: []*policypb.StoragePolicy{
   251  									&policypb.StoragePolicy{
   252  										Resolution: policypb.Resolution{
   253  											WindowSize: int64(10 * time.Second),
   254  											Precision:  int64(time.Second),
   255  										},
   256  										Retention: policypb.Retention{
   257  											Period: int64(24 * time.Hour),
   258  										},
   259  									},
   260  									&policypb.StoragePolicy{
   261  										Resolution: policypb.Resolution{
   262  											WindowSize: int64(5 * time.Minute),
   263  											Precision:  int64(time.Minute),
   264  										},
   265  										Retention: policypb.Retention{
   266  											Period: int64(48 * time.Hour),
   267  										},
   268  									},
   269  								},
   270  							},
   271  						},
   272  					},
   273  				},
   274  			},
   275  			&rulepb.RollupRule{
   276  				Uuid: "12669817-13ae-40e6-ba2f-33087b262c68",
   277  				Snapshots: []*rulepb.RollupRuleSnapshot{
   278  					&rulepb.RollupRuleSnapshot{
   279  						Name:         "foo",
   280  						Tombstoned:   false,
   281  						CutoverNanos: 12345,
   282  						Filter:       "tag1:value1 tag2:value2",
   283  						Tags: []*metricpb.Tag{
   284  							{
   285  								Name:  []byte("name"),
   286  								Value: []byte("name"),
   287  							},
   288  						},
   289  						TargetsV2: []*rulepb.RollupTargetV2{
   290  							&rulepb.RollupTargetV2{
   291  								Pipeline: &pipelinepb.Pipeline{
   292  									Ops: []pipelinepb.PipelineOp{
   293  										{
   294  											Type: pipelinepb.PipelineOp_ROLLUP,
   295  											Rollup: &pipelinepb.RollupOp{
   296  												NewName: "rName1",
   297  												Tags:    []string{"rtagName1", "rtagName2"},
   298  											},
   299  										},
   300  									},
   301  								},
   302  								StoragePolicies: []*policypb.StoragePolicy{
   303  									&policypb.StoragePolicy{
   304  										Resolution: policypb.Resolution{
   305  											WindowSize: int64(10 * time.Second),
   306  											Precision:  int64(time.Second),
   307  										},
   308  										Retention: policypb.Retention{
   309  											Period: int64(24 * time.Hour),
   310  										},
   311  									},
   312  								},
   313  							},
   314  						},
   315  					},
   316  					&rulepb.RollupRuleSnapshot{
   317  						Name:         "baz",
   318  						Tombstoned:   false,
   319  						CutoverNanos: 67890,
   320  						Filter:       "tag3:value3 tag4:value4",
   321  						Tags: []*metricpb.Tag{
   322  							{
   323  								Name:  []byte("name"),
   324  								Value: []byte("name"),
   325  							},
   326  						},
   327  						TargetsV2: []*rulepb.RollupTargetV2{
   328  							&rulepb.RollupTargetV2{
   329  								Pipeline: &pipelinepb.Pipeline{
   330  									Ops: []pipelinepb.PipelineOp{
   331  										{
   332  											Type: pipelinepb.PipelineOp_ROLLUP,
   333  											Rollup: &pipelinepb.RollupOp{
   334  												NewName: "rName1",
   335  												Tags:    []string{"rtagName1", "rtagName2"},
   336  												AggregationTypes: []aggregationpb.AggregationType{
   337  													aggregationpb.AggregationType_MEAN,
   338  												},
   339  											},
   340  										},
   341  									},
   342  								},
   343  								StoragePolicies: []*policypb.StoragePolicy{
   344  									&policypb.StoragePolicy{
   345  										Resolution: policypb.Resolution{
   346  											WindowSize: int64(time.Minute),
   347  											Precision:  int64(time.Minute),
   348  										},
   349  										Retention: policypb.Retention{
   350  											Period: int64(24 * time.Hour),
   351  										},
   352  									},
   353  									&policypb.StoragePolicy{
   354  										Resolution: policypb.Resolution{
   355  											WindowSize: int64(5 * time.Minute),
   356  											Precision:  int64(time.Minute),
   357  										},
   358  										Retention: policypb.Retention{
   359  											Period: int64(48 * time.Hour),
   360  										},
   361  									},
   362  								},
   363  							},
   364  						},
   365  					},
   366  				},
   367  			},
   368  			&rulepb.RollupRule{
   369  				Uuid: "12669817-13ae-40e6-ba2f-33087b262c68",
   370  				Snapshots: []*rulepb.RollupRuleSnapshot{
   371  					&rulepb.RollupRuleSnapshot{
   372  						Name:         "dup",
   373  						Tombstoned:   false,
   374  						CutoverNanos: 12345,
   375  						Filter:       "tag1:value1 tag2:value2",
   376  						Tags: []*metricpb.Tag{
   377  							{
   378  								Name:  []byte("name"),
   379  								Value: []byte("name"),
   380  							},
   381  						},
   382  						TargetsV2: []*rulepb.RollupTargetV2{
   383  							&rulepb.RollupTargetV2{
   384  								Pipeline: &pipelinepb.Pipeline{
   385  									Ops: []pipelinepb.PipelineOp{
   386  										{
   387  											Type: pipelinepb.PipelineOp_ROLLUP,
   388  											Rollup: &pipelinepb.RollupOp{
   389  												NewName: "rName1",
   390  												Tags:    []string{"rtagName1", "rtagName2"},
   391  											},
   392  										},
   393  									},
   394  								},
   395  								StoragePolicies: []*policypb.StoragePolicy{
   396  									&policypb.StoragePolicy{
   397  										Resolution: policypb.Resolution{
   398  											WindowSize: int64(10 * time.Second),
   399  											Precision:  int64(time.Second),
   400  										},
   401  										Retention: policypb.Retention{
   402  											Period: int64(24 * time.Hour),
   403  										},
   404  									},
   405  								},
   406  							},
   407  						},
   408  					},
   409  				},
   410  			},
   411  		},
   412  	}
   413  )
   414  
   415  func TestRuleSetKey(t *testing.T) {
   416  	s := testStore()
   417  	defer s.Close()
   418  
   419  	key := s.(*store).ruleSetKey(testNamespace)
   420  	require.Equal(t, "rules/fooNs", key)
   421  }
   422  
   423  func TestNewStore(t *testing.T) {
   424  	opts := NewStoreOptions(testNamespaceKey, testRuleSetKeyFmt, nil)
   425  	kvStore := mem.NewStore()
   426  	s := NewStore(kvStore, opts).(*store)
   427  	defer s.Close()
   428  
   429  	require.Equal(t, s.kvStore, kvStore)
   430  	require.Equal(t, s.opts, opts)
   431  }
   432  
   433  func TestReadNamespaces(t *testing.T) {
   434  	s := testStore()
   435  	defer s.Close()
   436  
   437  	_, e := s.(*store).kvStore.Set(testNamespaceKey, testNamespaces)
   438  	require.NoError(t, e)
   439  	nss, err := s.ReadNamespaces()
   440  	require.NoError(t, err)
   441  	require.NotNil(t, nss.Namespaces)
   442  }
   443  
   444  func TestReadNamespaceNotFound(t *testing.T) {
   445  	s := testStore()
   446  	defer s.Close()
   447  
   448  	_, err := s.ReadNamespaces()
   449  	require.IsType(t, merrors.NewNotFoundError(""), err)
   450  }
   451  
   452  func TestReadNamespacesError(t *testing.T) {
   453  	s := testStore()
   454  	defer s.Close()
   455  
   456  	_, e := s.(*store).kvStore.Set(testNamespaceKey, &rulepb.RollupRule{Uuid: "x"})
   457  	require.NoError(t, e)
   458  	nss, err := s.ReadNamespaces()
   459  	require.Error(t, err)
   460  	require.Nil(t, nss)
   461  }
   462  
   463  func TestReadRuleSet(t *testing.T) {
   464  	s := testStore()
   465  	defer s.Close()
   466  
   467  	_, e := s.(*store).kvStore.Set(testRuleSetKey, testRuleSet)
   468  	require.NoError(t, e)
   469  	rs, err := s.ReadRuleSet(testNamespace)
   470  	require.NoError(t, err)
   471  	require.NotNil(t, rs)
   472  }
   473  
   474  func TestReadRuleSetNotFound(t *testing.T) {
   475  	s := testStore()
   476  	defer s.Close()
   477  
   478  	_, err := s.ReadRuleSet(testNamespace)
   479  	require.IsType(t, merrors.NewNotFoundError(""), err)
   480  }
   481  
   482  func TestReadRuleSetError(t *testing.T) {
   483  	s := testStore()
   484  	defer s.Close()
   485  
   486  	_, e := s.(*store).kvStore.Set(testRuleSetKey, &rulepb.Namespace{Name: "x"})
   487  	require.NoError(t, e)
   488  	rs, err := s.ReadRuleSet("blah")
   489  	require.Error(t, err)
   490  	require.Nil(t, rs)
   491  }
   492  
   493  func TestWriteAll(t *testing.T) {
   494  	s := testStore()
   495  	defer s.Close()
   496  
   497  	rs, err := s.ReadRuleSet(testNamespaceKey)
   498  	require.Error(t, err)
   499  	require.Nil(t, rs)
   500  
   501  	nss, err := s.ReadNamespaces()
   502  	require.Error(t, err)
   503  	require.Nil(t, nss)
   504  
   505  	mutable := newMutableRuleSetFromProto(t, 0, testRuleSet)
   506  	namespaces, err := rules.NewNamespaces(0, testNamespaces)
   507  	require.NoError(t, err)
   508  
   509  	err = s.WriteAll(&namespaces, mutable)
   510  	require.NoError(t, err)
   511  
   512  	rs, err = s.ReadRuleSet(testNamespace)
   513  	require.NoError(t, err)
   514  	rsProto, err := rs.ToMutableRuleSet().Proto()
   515  	require.NoError(t, err)
   516  	require.Equal(t, rsProto, testRuleSet)
   517  
   518  	nss, err = s.ReadNamespaces()
   519  	require.NoError(t, err)
   520  	nssProto, err := nss.Proto()
   521  	require.NoError(t, err)
   522  	require.Equal(t, nssProto, testNamespaces)
   523  }
   524  
   525  func TestWriteAllValidationError(t *testing.T) {
   526  	errInvalidRuleSet := errors.New("invalid ruleset")
   527  	v := &mockValidator{
   528  		validateFn: func(rules.RuleSet) error { return errInvalidRuleSet },
   529  	}
   530  	s := testStoreWithValidator(v)
   531  	defer s.Close()
   532  	require.Equal(t, errInvalidRuleSet, s.WriteAll(nil, nil))
   533  }
   534  
   535  func TestWriteAllError(t *testing.T) {
   536  	s := testStore()
   537  	defer s.Close()
   538  
   539  	rs, err := s.ReadRuleSet(testNamespaceKey)
   540  	require.Error(t, err)
   541  	require.Nil(t, rs)
   542  
   543  	nss, err := s.ReadNamespaces()
   544  	require.Error(t, err)
   545  	require.Nil(t, nss)
   546  
   547  	mutable := newMutableRuleSetFromProto(t, 1, testRuleSet)
   548  	namespaces, err := rules.NewNamespaces(0, testNamespaces)
   549  	require.NoError(t, err)
   550  
   551  	type dataPair struct {
   552  		nss *rules.Namespaces
   553  		rs  rules.MutableRuleSet
   554  	}
   555  
   556  	otherNss, err := rules.NewNamespaces(1, testNamespaces)
   557  	require.NoError(t, err)
   558  
   559  	badPairs := []dataPair{
   560  		dataPair{nil, nil},
   561  		dataPair{nil, mutable},
   562  		dataPair{&namespaces, nil},
   563  		dataPair{&otherNss, mutable},
   564  	}
   565  
   566  	for _, p := range badPairs {
   567  		err = s.WriteAll(p.nss, p.rs)
   568  		require.Error(t, err)
   569  	}
   570  
   571  	_, err = s.ReadRuleSet(testNamespace)
   572  	require.Error(t, err)
   573  
   574  	_, err = s.ReadNamespaces()
   575  	require.Error(t, err)
   576  }
   577  
   578  func TestWriteRuleSetValidationError(t *testing.T) {
   579  	errInvalidRuleSet := errors.New("invalid ruleset")
   580  	v := &mockValidator{
   581  		validateFn: func(rules.RuleSet) error { return errInvalidRuleSet },
   582  	}
   583  	s := testStoreWithValidator(v)
   584  	defer s.Close()
   585  	require.Equal(t, errInvalidRuleSet, s.WriteRuleSet(nil))
   586  }
   587  
   588  func TestWriteRuleSetError(t *testing.T) {
   589  	s := testStore()
   590  	defer s.Close()
   591  
   592  	rs, err := s.ReadRuleSet(testNamespaceKey)
   593  	require.Error(t, err)
   594  	require.Nil(t, rs)
   595  
   596  	nss, err := s.ReadNamespaces()
   597  	require.Error(t, err)
   598  	require.Nil(t, nss)
   599  
   600  	mutable := newMutableRuleSetFromProto(t, 1, testRuleSet)
   601  	badRuleSets := []rules.MutableRuleSet{mutable, nil}
   602  	for _, rs := range badRuleSets {
   603  		err = s.WriteRuleSet(rs)
   604  		require.Error(t, err)
   605  	}
   606  
   607  	err = s.WriteRuleSet(nil)
   608  	require.Error(t, err)
   609  
   610  	_, err = s.ReadRuleSet(testNamespace)
   611  	require.Error(t, err)
   612  }
   613  
   614  func TestWriteRuleSetStaleDataError(t *testing.T) {
   615  	s := testStore()
   616  	defer s.Close()
   617  
   618  	mutable := newMutableRuleSetFromProto(t, 0, testRuleSet)
   619  	err := s.WriteRuleSet(mutable)
   620  	require.NoError(t, err)
   621  
   622  	jumpRuleSet := newMutableRuleSetFromProto(t, 5, testRuleSet)
   623  	err = s.WriteRuleSet(jumpRuleSet)
   624  	require.Error(t, err)
   625  	require.IsType(t, merrors.NewStaleDataError(""), err)
   626  }
   627  
   628  func TestWriteNamespace(t *testing.T) {
   629  	s := testStore()
   630  	defer s.Close()
   631  
   632  	nss, err := rules.NewNamespaces(0, testNamespaces)
   633  	require.NoError(t, err)
   634  
   635  	err = s.WriteNamespaces(&nss)
   636  	require.NoError(t, err)
   637  
   638  	existing, err := s.ReadNamespaces()
   639  	require.NoError(t, err)
   640  
   641  	revived, err := existing.AddNamespace(
   642  		"new",
   643  		rules.NewRuleSetUpdateHelper(0).NewUpdateMetadata(time.Now().UnixNano(), "test"),
   644  	)
   645  	require.NoError(t, err)
   646  	require.False(t, revived)
   647  
   648  	// Update should succeed
   649  	err = s.WriteNamespaces(existing)
   650  	require.NoError(t, err)
   651  }
   652  
   653  func TestWriteNamespaceError(t *testing.T) {
   654  	s := testStore()
   655  	defer s.Close()
   656  
   657  	err := s.WriteNamespaces(nil)
   658  	require.Error(t, err)
   659  }
   660  
   661  func TestWriteNamespacesStaleDataError(t *testing.T) {
   662  	s := testStore()
   663  	defer s.Close()
   664  
   665  	nss, err := rules.NewNamespaces(0, testNamespaces)
   666  	require.NoError(t, err)
   667  
   668  	// First write should succeed
   669  	err = s.WriteNamespaces(&nss)
   670  	require.NoError(t, err)
   671  
   672  	// writing again will encounter stale version
   673  	err = s.WriteNamespaces(&nss)
   674  	require.Error(t, err)
   675  	require.IsType(t, merrors.NewStaleDataError(""), err)
   676  }
   677  
   678  func TestWriteAllNoNamespace(t *testing.T) {
   679  	s := testStore()
   680  	defer s.Close()
   681  
   682  	rs, err := s.ReadRuleSet(testNamespaceKey)
   683  	require.Error(t, err)
   684  	require.Nil(t, rs)
   685  
   686  	nss, err := s.ReadNamespaces()
   687  	require.Error(t, err)
   688  	require.Nil(t, nss)
   689  
   690  	mutable := newMutableRuleSetFromProto(t, 0, testRuleSet)
   691  	namespaces, err := rules.NewNamespaces(0, testNamespaces)
   692  	require.NoError(t, err)
   693  
   694  	err = s.WriteAll(&namespaces, mutable)
   695  	require.NoError(t, err)
   696  
   697  	rs, err = s.ReadRuleSet(testNamespace)
   698  	require.NoError(t, err)
   699  
   700  	_, err = s.ReadNamespaces()
   701  	require.NoError(t, err)
   702  
   703  	err = s.WriteRuleSet(rs.ToMutableRuleSet())
   704  	require.NoError(t, err)
   705  
   706  	rs, err = s.ReadRuleSet(testNamespace)
   707  	require.NoError(t, err)
   708  	nss, err = s.ReadNamespaces()
   709  	require.NoError(t, err)
   710  	require.Equal(t, nss.Version(), 1)
   711  	require.Equal(t, rs.Version(), 2)
   712  }
   713  func TestWriteAllStaleDataError(t *testing.T) {
   714  	s := testStore()
   715  	defer s.Close()
   716  
   717  	mutable := newMutableRuleSetFromProto(t, 0, testRuleSet)
   718  	namespaces, err := rules.NewNamespaces(0, testNamespaces)
   719  	require.NoError(t, err)
   720  
   721  	err = s.WriteAll(&namespaces, mutable)
   722  	require.NoError(t, err)
   723  
   724  	jumpNamespaces, err := rules.NewNamespaces(5, testNamespaces)
   725  	require.NoError(t, err)
   726  	err = s.WriteAll(&jumpNamespaces, mutable)
   727  	require.Error(t, err)
   728  	require.IsType(t, merrors.NewStaleDataError(""), err)
   729  }
   730  
   731  func testStore() rules.Store {
   732  	return testStoreWithValidator(nil)
   733  }
   734  
   735  func testStoreWithValidator(validator rules.Validator) rules.Store {
   736  	opts := NewStoreOptions(testNamespaceKey, testRuleSetKeyFmt, validator)
   737  	kvStore := mem.NewStore()
   738  	return NewStore(kvStore, opts)
   739  }
   740  
   741  // newMutableRuleSetFromProto creates a new MutableRuleSet from a proto object.
   742  func newMutableRuleSetFromProto(
   743  	t *testing.T,
   744  	version int,
   745  	rs *rulepb.RuleSet,
   746  ) rules.MutableRuleSet {
   747  	// Takes a blank Options stuct because none of the mutation functions need the options.
   748  	roRuleSet, err := rules.NewRuleSetFromProto(version, rs, rules.NewOptions())
   749  	require.NoError(t, err)
   750  	return roRuleSet.ToMutableRuleSet()
   751  }
   752  
   753  type validateFn func(rs rules.RuleSet) error
   754  
   755  type mockValidator struct {
   756  	validateFn validateFn
   757  }
   758  
   759  func (v *mockValidator) Validate(rs rules.RuleSet) error              { return v.validateFn(rs) }
   760  func (v *mockValidator) ValidateSnapshot(snapshot view.RuleSet) error { return nil }
   761  func (v *mockValidator) Close()                                       {}