github.com/m3db/m3@v1.5.0/src/metrics/rules/validator/namespace/kv/validator.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 "sync" 27 "time" 28 29 "github.com/m3db/m3/src/cluster/kv" 30 kvutil "github.com/m3db/m3/src/cluster/kv/util" 31 "github.com/m3db/m3/src/metrics/rules/validator/namespace" 32 33 "go.uber.org/zap" 34 ) 35 36 var ( 37 errValidatorClosed = errors.New("validator is closed") 38 ) 39 40 type validator struct { 41 sync.RWMutex 42 43 opts NamespaceValidatorOptions 44 logger *zap.Logger 45 kvStore kv.Store 46 validNamespacesKey string 47 initWatchTimeout time.Duration 48 49 closed bool 50 doneCh chan struct{} 51 wg sync.WaitGroup 52 validNamespaces map[string]struct{} 53 } 54 55 // NewNamespaceValidator creates a new namespace validator. 56 func NewNamespaceValidator(opts NamespaceValidatorOptions) (namespace.Validator, error) { 57 v := &validator{ 58 opts: opts, 59 logger: opts.InstrumentOptions().Logger(), 60 kvStore: opts.KVStore(), 61 validNamespacesKey: opts.ValidNamespacesKey(), 62 initWatchTimeout: opts.InitWatchTimeout(), 63 doneCh: make(chan struct{}), 64 validNamespaces: toStringSet(opts.DefaultValidNamespaces()), 65 } 66 if err := v.watchRuntimeConfig(); err != nil { 67 return nil, err 68 } 69 return v, nil 70 } 71 72 // Validate validates whether a given namespace is valid. 73 func (v *validator) Validate(ns string) error { 74 v.RLock() 75 defer v.RUnlock() 76 77 if v.closed { 78 return errValidatorClosed 79 } 80 if _, exists := v.validNamespaces[ns]; !exists { 81 return fmt.Errorf("%s is not a valid namespace", ns) 82 } 83 return nil 84 } 85 86 func (v *validator) Close() { 87 v.Lock() 88 if v.closed { 89 v.Unlock() 90 return 91 } 92 v.closed = true 93 close(v.doneCh) 94 v.Unlock() 95 96 // NB: Must wait outside the lock to avoid a circular dependency 97 // between the goroutine closing the validator and the goroutine 98 // processing updates. 99 v.wg.Wait() 100 } 101 102 func (v *validator) watchRuntimeConfig() error { 103 watch, err := v.kvStore.Watch(v.validNamespacesKey) 104 if err != nil { 105 return err 106 } 107 kvOpts := kvutil.NewOptions().SetLogger(v.logger) 108 select { 109 case <-watch.C(): 110 v.processUpdate(watch.Get(), kvOpts) 111 case <-time.After(v.initWatchTimeout): 112 v.logger.Warn("timed out waiting for initial valid namespaces", 113 zap.String("key", v.validNamespacesKey), 114 zap.Duration("timeout", v.initWatchTimeout), 115 zap.Strings("default", v.opts.DefaultValidNamespaces()), 116 ) 117 } 118 119 v.wg.Add(1) 120 go func() { 121 defer v.wg.Done() 122 123 for { 124 select { 125 case <-watch.C(): 126 v.processUpdate(watch.Get(), kvOpts) 127 case <-v.doneCh: 128 return 129 } 130 } 131 }() 132 return nil 133 } 134 135 func (v *validator) processUpdate(value kv.Value, opts kvutil.Options) { 136 strs, err := kvutil.StringArrayFromValue(value, v.validNamespacesKey, v.opts.DefaultValidNamespaces(), opts) 137 if err != nil { 138 // kvutil already logged the error. 139 return 140 } 141 m := toStringSet(strs) 142 v.Lock() 143 v.validNamespaces = m 144 v.Unlock() 145 } 146 147 func toStringSet(strs []string) map[string]struct{} { 148 m := make(map[string]struct{}, len(strs)) 149 for _, s := range strs { 150 m[s] = struct{}{} 151 } 152 return m 153 }