istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/config/memory/store.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package memory provides an in-memory volatile config store implementation 16 package memory 17 18 import ( 19 "errors" 20 "fmt" 21 "sync" 22 "time" 23 24 "istio.io/istio/pilot/pkg/model" 25 "istio.io/istio/pkg/config" 26 "istio.io/istio/pkg/config/schema/collection" 27 ) 28 29 var ( 30 errNotFound = errors.New("item not found") 31 errAlreadyExists = errors.New("item already exists") 32 // TODO: can we make this compatible with kerror.IsConflict without imports the library? 33 errConflict = errors.New("conflicting resource version, try again") 34 ) 35 36 const ResourceVersion string = "ResourceVersion" 37 38 // Make creates an in-memory config store from a config schemas 39 // It is with validation 40 func Make(schemas collection.Schemas) model.ConfigStore { 41 return newStore(schemas, false) 42 } 43 44 // MakeSkipValidation creates an in-memory config store from a config schemas 45 // It is without validation 46 func MakeSkipValidation(schemas collection.Schemas) model.ConfigStore { 47 return newStore(schemas, true) 48 } 49 50 func newStore(schemas collection.Schemas, skipValidation bool) model.ConfigStore { 51 out := store{ 52 schemas: schemas, 53 data: make(map[config.GroupVersionKind]map[string]map[string]any), 54 skipValidation: skipValidation, 55 } 56 for _, s := range schemas.All() { 57 out.data[s.GroupVersionKind()] = make(map[string]map[string]any) 58 } 59 return &out 60 } 61 62 type store struct { 63 schemas collection.Schemas 64 data map[config.GroupVersionKind]map[string]map[string]any 65 skipValidation bool 66 mutex sync.RWMutex 67 } 68 69 func (cr *store) Schemas() collection.Schemas { 70 return cr.schemas 71 } 72 73 func (cr *store) Get(kind config.GroupVersionKind, name, namespace string) *config.Config { 74 cr.mutex.RLock() 75 defer cr.mutex.RUnlock() 76 _, ok := cr.data[kind] 77 if !ok { 78 return nil 79 } 80 81 ns, exists := cr.data[kind][namespace] 82 if !exists { 83 return nil 84 } 85 86 out, exists := ns[name] 87 if !exists { 88 return nil 89 } 90 config := out.(config.Config) 91 92 return &config 93 } 94 95 func (cr *store) List(kind config.GroupVersionKind, namespace string) []config.Config { 96 cr.mutex.RLock() 97 defer cr.mutex.RUnlock() 98 data, exists := cr.data[kind] 99 if !exists { 100 return nil 101 } 102 103 var size int 104 if namespace == "" { 105 for _, ns := range data { 106 size += len(ns) 107 } 108 } else { 109 size = len(data[namespace]) 110 } 111 112 out := make([]config.Config, 0, size) 113 if namespace == "" { 114 for _, ns := range data { 115 for _, value := range ns { 116 out = append(out, value.(config.Config)) 117 } 118 } 119 } else { 120 ns, exists := data[namespace] 121 if !exists { 122 return nil 123 } 124 for _, value := range ns { 125 out = append(out, value.(config.Config)) 126 } 127 } 128 return out 129 } 130 131 func (cr *store) Delete(kind config.GroupVersionKind, name, namespace string, resourceVersion *string) error { 132 cr.mutex.Lock() 133 defer cr.mutex.Unlock() 134 data, ok := cr.data[kind] 135 if !ok { 136 return fmt.Errorf("unknown type %v", kind) 137 } 138 ns, exists := data[namespace] 139 if !exists { 140 return errNotFound 141 } 142 143 _, exists = ns[name] 144 if !exists { 145 return errNotFound 146 } 147 148 delete(ns, name) 149 return nil 150 } 151 152 func (cr *store) Create(cfg config.Config) (string, error) { 153 cr.mutex.Lock() 154 defer cr.mutex.Unlock() 155 kind := cfg.GroupVersionKind 156 s, ok := cr.schemas.FindByGroupVersionKind(kind) 157 if !ok { 158 return "", fmt.Errorf("unknown type %v", kind) 159 } 160 if !cr.skipValidation { 161 if _, err := s.ValidateConfig(cfg); err != nil { 162 return "", err 163 } 164 } 165 ns, exists := cr.data[kind][cfg.Namespace] 166 if !exists { 167 ns = map[string]any{} 168 cr.data[kind][cfg.Namespace] = ns 169 } 170 171 _, exists = ns[cfg.Name] 172 173 if !exists { 174 tnow := time.Now() 175 if cfg.ResourceVersion == "" { 176 cfg.ResourceVersion = tnow.String() 177 } 178 // Set the creation timestamp, if not provided. 179 if cfg.CreationTimestamp.IsZero() { 180 cfg.CreationTimestamp = tnow 181 } 182 183 ns[cfg.Name] = cfg 184 return cfg.ResourceVersion, nil 185 } 186 return "", errAlreadyExists 187 } 188 189 func (cr *store) Update(cfg config.Config) (string, error) { 190 cr.mutex.Lock() 191 defer cr.mutex.Unlock() 192 kind := cfg.GroupVersionKind 193 s, ok := cr.schemas.FindByGroupVersionKind(kind) 194 if !ok { 195 return "", fmt.Errorf("unknown type %v", kind) 196 } 197 if !cr.skipValidation { 198 if _, err := s.ValidateConfig(cfg); err != nil { 199 return "", err 200 } 201 } 202 203 ns, exists := cr.data[kind][cfg.Namespace] 204 if !exists { 205 return "", errNotFound 206 } 207 208 existing, exists := ns[cfg.Name] 209 if !exists { 210 return "", errNotFound 211 } 212 if hasConflict(existing.(config.Config), cfg) { 213 return "", errConflict 214 } 215 if cfg.Annotations != nil && cfg.Annotations[ResourceVersion] != "" { 216 cfg.ResourceVersion = cfg.Annotations[ResourceVersion] 217 delete(cfg.Annotations, ResourceVersion) 218 } else { 219 cfg.ResourceVersion = time.Now().String() 220 } 221 222 ns[cfg.Name] = cfg 223 return cfg.ResourceVersion, nil 224 } 225 226 func (cr *store) UpdateStatus(cfg config.Config) (string, error) { 227 return cr.Update(cfg) 228 } 229 230 func (cr *store) Patch(orig config.Config, patchFn config.PatchFunc) (string, error) { 231 cr.mutex.Lock() 232 defer cr.mutex.Unlock() 233 234 gvk := orig.GroupVersionKind 235 s, ok := cr.schemas.FindByGroupVersionKind(gvk) 236 if !ok { 237 return "", fmt.Errorf("unknown type %v", gvk) 238 } 239 240 cfg, _ := patchFn(orig) 241 if !cr.skipValidation { 242 if _, err := s.ValidateConfig(cfg); err != nil { 243 return "", err 244 } 245 } 246 247 _, ok = cr.data[gvk] 248 if !ok { 249 return "", errNotFound 250 } 251 ns, exists := cr.data[gvk][orig.Namespace] 252 if !exists { 253 return "", errNotFound 254 } 255 256 rev := time.Now().String() 257 cfg.ResourceVersion = rev 258 ns[cfg.Name] = cfg 259 260 return rev, nil 261 } 262 263 // hasConflict checks if the two resources have a conflict, which will block Update calls 264 func hasConflict(existing, replacement config.Config) bool { 265 if replacement.ResourceVersion == "" { 266 // We don't care about resource version, so just always overwrite 267 return false 268 } 269 // We set a resource version but its not matched, it is a conflict 270 if replacement.ResourceVersion != existing.ResourceVersion { 271 return true 272 } 273 return false 274 }