1
// Copyright Istio Authors
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
7
// http://www.apache.org/licenses/LICENSE-2.0
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.
15
// Package memory provides an in-memory volatile config store implementation
24
"istio.io/istio/pilot/pkg/model"
25
"istio.io/istio/pkg/config"
26
"istio.io/istio/pkg/config/schema/collection"
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")
36
const ResourceVersion string = "ResourceVersion"
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)
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)
50
func newStore(schemas collection.Schemas, skipValidation bool) model.ConfigStore {
53
data: make(map[config.GroupVersionKind]map[string]map[string]any),
54
skipValidation: skipValidation,
56
for _, s := range schemas.All() {
57
out.data[s.GroupVersionKind()] = make(map[string]map[string]any)
63
schemas collection.Schemas
64
data map[config.GroupVersionKind]map[string]map[string]any
69
func (cr *store) Schemas() collection.Schemas {
73
func (cr *store) Get(kind config.GroupVersionKind, name, namespace string) *config.Config {
75
defer cr.mutex.RUnlock()
76
_, ok := cr.data[kind]
81
ns, exists := cr.data[kind][namespace]
86
out, exists := ns[name]
90
config := out.(config.Config)
95
func (cr *store) List(kind config.GroupVersionKind, namespace string) []config.Config {
97
defer cr.mutex.RUnlock()
98
data, exists := cr.data[kind]
105
for _, ns := range data {
109
size = len(data[namespace])
112
out := make([]config.Config, 0, size)
114
for _, ns := range data {
115
for _, value := range ns {
116
out = append(out, value.(config.Config))
120
ns, exists := data[namespace]
124
for _, value := range ns {
125
out = append(out, value.(config.Config))
131
func (cr *store) Delete(kind config.GroupVersionKind, name, namespace string, resourceVersion *string) error {
133
defer cr.mutex.Unlock()
134
data, ok := cr.data[kind]
136
return fmt.Errorf("unknown type %v", kind)
138
ns, exists := data[namespace]
152
func (cr *store) Create(cfg config.Config) (string, error) {
154
defer cr.mutex.Unlock()
155
kind := cfg.GroupVersionKind
156
s, ok := cr.schemas.FindByGroupVersionKind(kind)
158
return "", fmt.Errorf("unknown type %v", kind)
160
if !cr.skipValidation {
161
if _, err := s.ValidateConfig(cfg); err != nil {
165
ns, exists := cr.data[kind][cfg.Namespace]
167
ns = map[string]any{}
168
cr.data[kind][cfg.Namespace] = ns
171
_, exists = ns[cfg.Name]
175
if cfg.ResourceVersion == "" {
176
cfg.ResourceVersion = tnow.String()
178
// Set the creation timestamp, if not provided.
179
if cfg.CreationTimestamp.IsZero() {
180
cfg.CreationTimestamp = tnow
184
return cfg.ResourceVersion, nil
186
return "", errAlreadyExists
189
func (cr *store) Update(cfg config.Config) (string, error) {
191
defer cr.mutex.Unlock()
192
kind := cfg.GroupVersionKind
193
s, ok := cr.schemas.FindByGroupVersionKind(kind)
195
return "", fmt.Errorf("unknown type %v", kind)
197
if !cr.skipValidation {
198
if _, err := s.ValidateConfig(cfg); err != nil {
203
ns, exists := cr.data[kind][cfg.Namespace]
205
return "", errNotFound
208
existing, exists := ns[cfg.Name]
210
return "", errNotFound
212
if hasConflict(existing.(config.Config), cfg) {
213
return "", errConflict
215
if cfg.Annotations != nil && cfg.Annotations[ResourceVersion] != "" {
216
cfg.ResourceVersion = cfg.Annotations[ResourceVersion]
217
delete(cfg.Annotations, ResourceVersion)
219
cfg.ResourceVersion = time.Now().String()
223
return cfg.ResourceVersion, nil
226
func (cr *store) UpdateStatus(cfg config.Config) (string, error) {
227
return cr.Update(cfg)
230
func (cr *store) Patch(orig config.Config, patchFn config.PatchFunc) (string, error) {
232
defer cr.mutex.Unlock()
234
gvk := orig.GroupVersionKind
235
s, ok := cr.schemas.FindByGroupVersionKind(gvk)
237
return "", fmt.Errorf("unknown type %v", gvk)
240
cfg, _ := patchFn(orig)
241
if !cr.skipValidation {
242
if _, err := s.ValidateConfig(cfg); err != nil {
249
return "", errNotFound
251
ns, exists := cr.data[gvk][orig.Namespace]
253
return "", errNotFound
256
rev := time.Now().String()
257
cfg.ResourceVersion = rev
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
269
// We set a resource version but its not matched, it is a conflict
270
if replacement.ResourceVersion != existing.ResourceVersion {