github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/metrics/matcher/namespaces_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 matcher
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"sync"
    27  	"sync/atomic"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/m3db/m3/src/cluster/kv"
    32  	"github.com/m3db/m3/src/cluster/kv/mem"
    33  	"github.com/m3db/m3/src/metrics/generated/proto/rulepb"
    34  	"github.com/m3db/m3/src/metrics/matcher/cache"
    35  	"github.com/m3db/m3/src/metrics/matcher/namespace"
    36  	"github.com/m3db/m3/src/metrics/metric/id"
    37  	"github.com/m3db/m3/src/metrics/rules"
    38  
    39  	"github.com/golang/protobuf/proto"
    40  	"github.com/stretchr/testify/require"
    41  )
    42  
    43  const (
    44  	testNamespacesKey = "testNamespaces"
    45  )
    46  
    47  func TestNamespacesWatchAndClose(t *testing.T) {
    48  	store, _, nss, _ := testNamespaces()
    49  	proto := &rulepb.Namespaces{
    50  		Namespaces: []*rulepb.Namespace{
    51  			{
    52  				Name: "fooNs",
    53  				Snapshots: []*rulepb.NamespaceSnapshot{
    54  					{
    55  						ForRulesetVersion: 1,
    56  						Tombstoned:        true,
    57  					},
    58  				},
    59  			},
    60  		},
    61  	}
    62  	_, err := store.SetIfNotExists(testNamespacesKey, proto)
    63  	require.NoError(t, err)
    64  	require.NoError(t, nss.Watch())
    65  	require.Equal(t, 1, nss.rules.Len())
    66  	nss.Close()
    67  }
    68  
    69  func TestNamespacesWatchSoftErr(t *testing.T) {
    70  	_, _, nss, _ := testNamespaces()
    71  	// No value set, so this will soft error
    72  	require.NoError(t, nss.Open())
    73  }
    74  
    75  func TestNamespacesWatchRulesetSoftErr(t *testing.T) {
    76  	store, _, nss, _ := testNamespaces()
    77  	proto := &rulepb.Namespaces{
    78  		Namespaces: []*rulepb.Namespace{
    79  			{
    80  				Name: "fooNs",
    81  				Snapshots: []*rulepb.NamespaceSnapshot{
    82  					{
    83  						ForRulesetVersion: 1,
    84  						Tombstoned:        true,
    85  					},
    86  				},
    87  			},
    88  		},
    89  	}
    90  	_, err := store.SetIfNotExists(testNamespacesKey, proto)
    91  	require.NoError(t, err)
    92  
    93  	// This should also soft error even though the underlying ruleset does not exist
    94  	require.NoError(t, nss.Open())
    95  }
    96  
    97  func TestNamespacesWatchHardErr(t *testing.T) {
    98  	_, _, _, opts := testNamespaces()
    99  	opts = opts.SetRequireNamespaceWatchOnInit(true)
   100  	nss := NewNamespaces(testNamespacesKey, opts).(*namespaces)
   101  	// This should hard error with RequireNamespaceWatchOnInit enabled
   102  	require.Error(t, nss.Open())
   103  }
   104  
   105  func TestNamespacesWatchRulesetHardErr(t *testing.T) {
   106  	store, _, _, opts := testNamespaces()
   107  	opts = opts.SetRequireNamespaceWatchOnInit(true)
   108  	nss := NewNamespaces(testNamespacesKey, opts).(*namespaces)
   109  
   110  	proto := &rulepb.Namespaces{
   111  		Namespaces: []*rulepb.Namespace{
   112  			{
   113  				Name: "fooNs",
   114  				Snapshots: []*rulepb.NamespaceSnapshot{
   115  					{
   116  						ForRulesetVersion: 1,
   117  						Tombstoned:        true,
   118  					},
   119  				},
   120  			},
   121  		},
   122  	}
   123  	_, err := store.SetIfNotExists(testNamespacesKey, proto)
   124  	require.NoError(t, err)
   125  
   126  	// This should also hard error with RequireNamespaceWatchOnInit enabled,
   127  	// because the underlying ruleset does not exist
   128  	require.Error(t, nss.Open())
   129  }
   130  
   131  func TestNamespacesOpenWithInterrupt(t *testing.T) {
   132  	interruptedCh := make(chan struct{}, 1)
   133  	interruptedCh <- struct{}{}
   134  
   135  	_, _, nss, _ := testNamespacesWithInterruptedCh(interruptedCh)
   136  	err := nss.Open()
   137  
   138  	require.Error(t, err)
   139  	require.Equal(t, err.Error(), "interrupted")
   140  }
   141  
   142  func TestToNamespacesNilValue(t *testing.T) {
   143  	_, _, nss, _ := testNamespaces()
   144  	_, err := nss.toNamespaces(nil)
   145  	require.Equal(t, errNilValue, err)
   146  }
   147  
   148  func TestToNamespacesUnmarshalError(t *testing.T) {
   149  	_, _, nss, _ := testNamespaces()
   150  	_, err := nss.toNamespaces(&mockValue{})
   151  	require.Error(t, err)
   152  }
   153  
   154  func TestToNamespacesSuccess(t *testing.T) {
   155  	store, _, nss, _ := testNamespaces()
   156  	proto := &rulepb.Namespaces{
   157  		Namespaces: []*rulepb.Namespace{
   158  			{
   159  				Name: "fooNs",
   160  				Snapshots: []*rulepb.NamespaceSnapshot{
   161  					{
   162  						ForRulesetVersion: 1,
   163  						Tombstoned:        true,
   164  					},
   165  				},
   166  			},
   167  		},
   168  	}
   169  	_, err := store.SetIfNotExists(testNamespacesKey, proto)
   170  	require.NoError(t, err)
   171  	v, err := store.Get(testNamespacesKey)
   172  	require.NoError(t, err)
   173  	res, err := nss.toNamespaces(v)
   174  	require.NoError(t, err)
   175  	actual := res.(rules.Namespaces)
   176  	require.Equal(t, 1, actual.Version())
   177  	require.Equal(t, 1, len(actual.Namespaces()))
   178  	require.Equal(t, []byte("fooNs"), actual.Namespaces()[0].Name())
   179  }
   180  
   181  func TestNamespacesProcess(t *testing.T) {
   182  	_, cache, nss, opts := testNamespaces()
   183  	c := cache.(*memCache)
   184  
   185  	for _, input := range []struct {
   186  		namespace  []byte
   187  		id         string
   188  		version    int
   189  		tombstoned bool
   190  	}{
   191  		{namespace: []byte("fooNs"), id: "foo", version: 1, tombstoned: false},
   192  		{namespace: []byte("barNs"), id: "bar", version: 1, tombstoned: true},
   193  		{namespace: []byte("catNs"), id: "cat", version: 3, tombstoned: true},
   194  		{namespace: []byte("lolNs"), id: "lol", version: 3, tombstoned: true},
   195  	} {
   196  		rs := newRuleSet(input.namespace, input.id, opts).(*ruleSet)
   197  		rs.Value = &mockRuntimeValue{key: input.id}
   198  		rs.version = input.version
   199  		rs.tombstoned = input.tombstoned
   200  		nss.rules.Set(input.namespace, rs)
   201  		c.namespaces[string(input.namespace)] = memResults{source: rs}
   202  	}
   203  
   204  	update := &rulepb.Namespaces{
   205  		Namespaces: []*rulepb.Namespace{
   206  			{
   207  				Name: "fooNs",
   208  				Snapshots: []*rulepb.NamespaceSnapshot{
   209  					{
   210  						ForRulesetVersion: 1,
   211  						Tombstoned:        false,
   212  					},
   213  					{
   214  						ForRulesetVersion: 2,
   215  						Tombstoned:        false,
   216  					},
   217  				},
   218  			},
   219  			{
   220  				Name: "barNs",
   221  				Snapshots: []*rulepb.NamespaceSnapshot{
   222  					{
   223  						ForRulesetVersion: 1,
   224  						Tombstoned:        false,
   225  					},
   226  					{
   227  						ForRulesetVersion: 2,
   228  						Tombstoned:        true,
   229  					},
   230  				},
   231  			},
   232  			{
   233  				Name: "bazNs",
   234  				Snapshots: []*rulepb.NamespaceSnapshot{
   235  					{
   236  						ForRulesetVersion: 1,
   237  						Tombstoned:        false,
   238  					},
   239  					{
   240  						ForRulesetVersion: 2,
   241  						Tombstoned:        false,
   242  					},
   243  				},
   244  			},
   245  			{
   246  				Name: "catNs",
   247  				Snapshots: []*rulepb.NamespaceSnapshot{
   248  					{
   249  						ForRulesetVersion: 3,
   250  						Tombstoned:        true,
   251  					},
   252  				},
   253  			},
   254  			{
   255  				Name:      "mehNs",
   256  				Snapshots: nil,
   257  			},
   258  		},
   259  	}
   260  
   261  	nssValue, err := rules.NewNamespaces(5, update)
   262  	require.NoError(t, err)
   263  	require.NoError(t, nss.process(nssValue))
   264  	require.Equal(t, 5, nss.rules.Len())
   265  	require.Equal(t, 5, len(c.namespaces))
   266  
   267  	expected := []struct {
   268  		key       string
   269  		watched   int32
   270  		unwatched int32
   271  	}{
   272  		{key: "foo", watched: 1, unwatched: 0},
   273  		{key: "bar", watched: 1, unwatched: 0},
   274  		{key: "/ruleset/bazNs", watched: 1, unwatched: 0},
   275  		{key: "cat", watched: 0, unwatched: 1},
   276  		{key: "/ruleset/mehNs", watched: 1, unwatched: 0},
   277  	}
   278  	for i, ns := range update.Namespaces {
   279  		rs, exists := nss.rules.Get([]byte(ns.Name))
   280  		ruleSet := rs.(*ruleSet)
   281  		require.True(t, exists)
   282  		require.Equal(t, expected[i].key, rs.Key())
   283  		require.Equal(t, ruleSet, c.namespaces[string(ns.Name)].source)
   284  		mv, ok := ruleSet.Value.(*mockRuntimeValue)
   285  		if !ok {
   286  			continue
   287  		}
   288  		require.Equal(t, expected[i].watched, mv.numWatched)
   289  		require.Equal(t, expected[i].unwatched, mv.numUnwatched)
   290  	}
   291  }
   292  
   293  func testNamespacesWithInterruptedCh(interruptedCh chan struct{}) (kv.TxnStore, cache.Cache, *namespaces, Options) {
   294  	store := mem.NewStore()
   295  	cache := newMemCache()
   296  	opts := NewOptions().
   297  		SetInitWatchTimeout(100 * time.Millisecond).
   298  		SetKVStore(store).
   299  		SetNamespacesKey(testNamespacesKey).
   300  		SetMatchRangePast(0).
   301  		SetOnNamespaceAddedFn(func(namespace []byte, ruleSet RuleSet) {
   302  			cache.Register(namespace, ruleSet)
   303  		}).
   304  		SetOnNamespaceRemovedFn(func(namespace []byte) {
   305  			cache.Unregister(namespace)
   306  		}).
   307  		SetOnRuleSetUpdatedFn(func(namespace []byte, ruleSet RuleSet) {
   308  			cache.Register(namespace, ruleSet)
   309  		}).
   310  		SetInterruptedCh(interruptedCh)
   311  
   312  	return store, cache, NewNamespaces(testNamespacesKey, opts).(*namespaces), opts
   313  }
   314  
   315  func testNamespaces() (kv.TxnStore, cache.Cache, *namespaces, Options) {
   316  	return testNamespacesWithInterruptedCh(nil)
   317  }
   318  
   319  type memResults struct {
   320  	results map[string]rules.MatchResult
   321  	source  rules.Matcher
   322  }
   323  
   324  type memCache struct {
   325  	sync.RWMutex
   326  	nsResolver namespace.Resolver
   327  	namespaces map[string]memResults
   328  }
   329  
   330  func newMemCache() cache.Cache {
   331  	return &memCache{namespaces: make(map[string]memResults), nsResolver: namespace.Default}
   332  }
   333  
   334  func (c *memCache) ForwardMatch(id id.ID, _, _ int64, _ rules.MatchOptions) (rules.MatchResult, error) {
   335  	c.RLock()
   336  	defer c.RUnlock()
   337  	if results, exists := c.namespaces[string(c.nsResolver.Resolve(id))]; exists {
   338  		return results.results[string(id.Bytes())], nil
   339  	}
   340  	return rules.EmptyMatchResult, nil
   341  }
   342  
   343  func (c *memCache) Register(namespace []byte, source rules.Matcher) {
   344  	c.Lock()
   345  	defer c.Unlock()
   346  
   347  	results, exists := c.namespaces[string(namespace)]
   348  	if !exists {
   349  		results = memResults{
   350  			results: make(map[string]rules.MatchResult),
   351  			source:  source,
   352  		}
   353  		c.namespaces[string(namespace)] = results
   354  		return
   355  	}
   356  	panic(fmt.Errorf("re-registering existing namespace %s", namespace))
   357  }
   358  
   359  func (c *memCache) Refresh(namespace []byte, source rules.Matcher) {
   360  	c.Lock()
   361  	defer c.Unlock()
   362  
   363  	results, exists := c.namespaces[string(namespace)]
   364  	if !exists || results.source != source {
   365  		return
   366  	}
   367  	c.namespaces[string(namespace)] = memResults{
   368  		results: make(map[string]rules.MatchResult),
   369  		source:  source,
   370  	}
   371  }
   372  
   373  func (c *memCache) Unregister(namespace []byte) {
   374  	c.Lock()
   375  	defer c.Unlock()
   376  	delete(c.namespaces, string(namespace))
   377  }
   378  
   379  func (c *memCache) Close() error { return nil }
   380  
   381  type mockRuntimeValue struct {
   382  	key          string
   383  	numWatched   int32
   384  	numUnwatched int32
   385  }
   386  
   387  func (mv *mockRuntimeValue) Key() string  { return mv.key }
   388  func (mv *mockRuntimeValue) Watch() error { atomic.AddInt32(&mv.numWatched, 1); return nil }
   389  func (mv *mockRuntimeValue) Unwatch()     { atomic.AddInt32(&mv.numUnwatched, 1) }
   390  
   391  type mockValue struct {
   392  	version int
   393  }
   394  
   395  func (v mockValue) Unmarshal(proto.Message) error { return errors.New("unimplemented") }
   396  func (v mockValue) Version() int                  { return v.version }
   397  func (v mockValue) IsNewer(other kv.Value) bool   { return v.version > other.Version() }