pkg/controller/state/state.go (174 lines of code) (raw):

/* Copyright 2020 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package stage stores controller state and persists it in a ConfigMap. package state import ( "context" "encoding/json" "fmt" "sync" api "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/klog" "github.com/GoogleCloudPlatform/gke-managed-certs/pkg/clients/configmap" "github.com/GoogleCloudPlatform/gke-managed-certs/pkg/utils/errors" "github.com/GoogleCloudPlatform/gke-managed-certs/pkg/utils/types" ) const ( configMapName = "managed-certificate-config" configMapNamespace = "kube-system" ) type Entry struct { ExcludedFromSLO bool SoftDeleted bool SslCertificateName string SslCertificateBindingReported bool SslCertificateCreationReported bool } type Interface interface { Delete(ctx context.Context, id types.Id) Get(id types.Id) (Entry, error) Insert(ctx context.Context, id types.Id, sslCertificateName string) List() map[types.Id]Entry SetExcludedFromSLO(ctx context.Context, id types.Id) error SetSoftDeleted(ctx context.Context, id types.Id, value bool) error SetSslCertificateBindingReported(ctx context.Context, id types.Id) error SetSslCertificateCreationReported(ctx context.Context, id types.Id) error } type impl struct { sync.RWMutex // Maps ManagedCertificate to SslCertificate mapping map[types.Id]Entry // Manages ConfigMap objects configmap configmap.Interface } func New(ctx context.Context, configmap configmap.Interface) Interface { mapping := make(map[types.Id]Entry) config, err := configmap.Get(ctx, configMapNamespace, configMapName) if err != nil { klog.Warning(err) } else if len(config.Data) > 0 { mapping = unmarshal(config.Data) } return &impl{ mapping: mapping, configmap: configmap, } } // Delete deletes entry associated with ManagedCertificate id. func (state *impl) Delete(ctx context.Context, id types.Id) { state.Lock() defer state.Unlock() delete(state.mapping, id) state.persist(ctx) } // Get fetches an entry associated with ManagedCertificate id. func (state *impl) Get(id types.Id) (Entry, error) { state.Lock() defer state.Unlock() entry, exists := state.mapping[id] if !exists { return Entry{}, errors.NotFound } return entry, nil } // Insert adds a new entry with an associated SslCertificate name. // If an id already exists in state, it is overwritten. func (state *impl) Insert(ctx context.Context, id types.Id, sslCertificateName string) { state.Lock() defer state.Unlock() v, exists := state.mapping[id] if !exists { v = Entry{} } v.SslCertificateName = sslCertificateName state.mapping[id] = v state.persist(ctx) } // List fetches all data stored in state. func (state *impl) List() map[types.Id]Entry { data := make(map[types.Id]Entry, 0) state.RLock() defer state.RUnlock() for id, entry := range state.mapping { data[id] = entry } return data } // SetExcludedFromSLO sets to true a flag indicating that entry associated // with given ManagedCertificate id should not be taken into account // for the purposes of SLO calculation. func (state *impl) SetExcludedFromSLO(ctx context.Context, id types.Id) error { state.Lock() defer state.Unlock() v, exists := state.mapping[id] if !exists { return errors.NotFound } v.ExcludedFromSLO = true state.mapping[id] = v state.persist(ctx) return nil } // SetSoftDeleted sets `value` to a flag indicating that entry associated // with given ManagedCertificate id has been deleted. func (state *impl) SetSoftDeleted(ctx context.Context, id types.Id, value bool) error { state.Lock() defer state.Unlock() v, exists := state.mapping[id] if !exists { return errors.NotFound } v.SoftDeleted = value state.mapping[id] = v state.persist(ctx) return nil } // SetSslCertificateBindingReported sets to true a flag indicating that // SslCertificate binding metric has been already reported // for this ManagedCertificate id. func (state *impl) SetSslCertificateBindingReported(ctx context.Context, id types.Id) error { state.Lock() defer state.Unlock() v, exists := state.mapping[id] if !exists { return errors.NotFound } v.SslCertificateBindingReported = true state.mapping[id] = v state.persist(ctx) return nil } // SetSslCertificateCreationReported sets to true a flag indicating that // SslCertificate creation metric has been already reported // for this ManagedCertificate id. func (state *impl) SetSslCertificateCreationReported(ctx context.Context, id types.Id) error { state.Lock() defer state.Unlock() v, exists := state.mapping[id] if !exists { return errors.NotFound } v.SslCertificateCreationReported = true state.mapping[id] = v state.persist(ctx) return nil } func (state *impl) persist(ctx context.Context) { config := &api.ConfigMap{ Data: marshal(state.mapping), ObjectMeta: metav1.ObjectMeta{ Name: configMapName, }, } if err := state.configmap.UpdateOrCreate(ctx, configMapNamespace, config); err != nil { runtime.HandleError(err) } } // jsonMapEntry stores an entry in a map being marshalled to JSON. type jsonMapEntry struct { Key types.Id Value Entry } // Transforms input map m into a new map which can be stored in a ConfigMap. // Values in new map encode entries of m. func marshal(m map[types.Id]Entry) map[string]string { result := make(map[string]string) i := 0 for k, v := range m { i++ key := fmt.Sprintf("%d", i) value, _ := json.Marshal(jsonMapEntry{ Key: k, Value: v, }) result[key] = string(value) } return result } // Transforms an encoded map back into initial map. func unmarshal(m map[string]string) map[types.Id]Entry { result := make(map[types.Id]Entry) for _, v := range m { var entry jsonMapEntry _ = json.Unmarshal([]byte(v), &entry) result[entry.Key] = entry.Value } return result }