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() }