github.com/kubewharf/katalyst-core@v0.5.3/pkg/metaserver/kcc/config.go (about)

     1  /*
     2  Copyright 2022 The Katalyst Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package config is the package that gets centralized configurations periodically
    18  // and dynamically for a given node.
    19  package kcc // import "github.com/kubewharf/katalyst-core/pkg/metaserver/config"
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"sync"
    25  	"time"
    26  
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/client-go/dynamic"
    29  	"k8s.io/klog/v2"
    30  
    31  	"github.com/kubewharf/katalyst-api/pkg/apis/config/v1alpha1"
    32  	"github.com/kubewharf/katalyst-core/pkg/client"
    33  	"github.com/kubewharf/katalyst-core/pkg/metaserver/agent/cnc"
    34  	"github.com/kubewharf/katalyst-core/pkg/util"
    35  	"github.com/kubewharf/katalyst-core/pkg/util/native"
    36  )
    37  
    38  // ConfigurationLoader is used to load configurations from centralized server.
    39  type ConfigurationLoader interface {
    40  	LoadConfig(ctx context.Context, gvr metav1.GroupVersionResource, conf interface{}) error
    41  }
    42  
    43  // configCache keeps a local in-memory cache for each configuration CRD.
    44  // each time when users want to get the latest configuration, return from
    45  // cache firstly (it still valid); otherwise, trigger a client getting action.
    46  type configCache struct {
    47  	// targetConfigHash records the config hash for matched configurations in CNC.
    48  	targetConfigHash string
    49  	// targetConfigContent records the contents for matched configurations in CNC.
    50  	targetConfigContent util.KCCTargetResource
    51  }
    52  
    53  type katalystCustomConfigLoader struct {
    54  	client     *client.GenericClientSet
    55  	cncFetcher cnc.CNCFetcher
    56  
    57  	ttl time.Duration
    58  
    59  	mux sync.RWMutex
    60  
    61  	// lastFetchConfigTime is to limit the rate of getting each configuration,
    62  	// this is to avoid getting some configurations frequently when it always
    63  	// fails
    64  	lastFetchConfigTime map[metav1.GroupVersionResource]time.Time
    65  
    66  	// configCache is a cache of gvr to current target config meta-info
    67  	// and its latest object
    68  	configCache map[metav1.GroupVersionResource]configCache
    69  }
    70  
    71  // NewKatalystCustomConfigLoader create a new configManager to fetch KatalystCustomConfig.
    72  // every LoadConfig() call tries to fetch the value from local cache; if it is
    73  // not there, invalidated or too old, we fetch it from api-server and refresh the
    74  // value in cache; otherwise it is just fetched from cache.
    75  // defaultGVRList s the list of default gvr fetched from remote api-server, if
    76  // LoadConfig() fetches a new gvr, it will be automatically added to.
    77  func NewKatalystCustomConfigLoader(clientSet *client.GenericClientSet, ttl time.Duration,
    78  	cncFetcher cnc.CNCFetcher,
    79  ) ConfigurationLoader {
    80  	return &katalystCustomConfigLoader{
    81  		cncFetcher:          cncFetcher,
    82  		client:              clientSet,
    83  		ttl:                 ttl,
    84  		lastFetchConfigTime: make(map[metav1.GroupVersionResource]time.Time),
    85  		configCache:         make(map[metav1.GroupVersionResource]configCache),
    86  	}
    87  }
    88  
    89  func (c *katalystCustomConfigLoader) LoadConfig(ctx context.Context, gvr metav1.GroupVersionResource, conf interface{}) error {
    90  	// get target config from updated cnc
    91  	targetConfig, err := c.getCNCTargetConfig(ctx, gvr)
    92  	if err != nil {
    93  		return fmt.Errorf("get cnc target cache failed: %v", err)
    94  	}
    95  
    96  	// update current target config according to its hash
    97  	err = c.updateConfigCacheIfNeed(ctx, targetConfig)
    98  	if err != nil {
    99  		klog.Errorf("[kcc-sdk] failed update config cache from remote: %s, use local cache instead", err)
   100  	}
   101  
   102  	c.mux.RLock()
   103  	cache, ok := c.configCache[gvr]
   104  	c.mux.RUnlock()
   105  
   106  	if ok {
   107  		return cache.targetConfigContent.Unmarshal(conf)
   108  	}
   109  
   110  	return fmt.Errorf("get config cache for %s not found", gvr)
   111  }
   112  
   113  // getCNCTargetConfig get cnc target from cnc fetcher
   114  func (c *katalystCustomConfigLoader) getCNCTargetConfig(ctx context.Context, gvr metav1.GroupVersionResource) (*v1alpha1.TargetConfig, error) {
   115  	currentCNC, err := c.cncFetcher.GetCNC(ctx)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	for _, target := range currentCNC.Status.KatalystCustomConfigList {
   121  		if target.ConfigType == gvr {
   122  			return &target, nil
   123  		}
   124  	}
   125  
   126  	return nil, fmt.Errorf("get target config %s not found", gvr)
   127  }
   128  
   129  // updateConfigCacheIfNeed checks if the previous configuration has changed, and
   130  // re-get from APIServer if the previous is out-of date.
   131  func (c *katalystCustomConfigLoader) updateConfigCacheIfNeed(ctx context.Context, targetConfig *v1alpha1.TargetConfig) error {
   132  	c.mux.Lock()
   133  	defer c.mux.Unlock()
   134  
   135  	if targetConfig == nil {
   136  		return nil
   137  	}
   138  
   139  	gvr := targetConfig.ConfigType
   140  	if cache, ok := c.configCache[gvr]; !ok || targetConfig.Hash != cache.targetConfigHash {
   141  		// update last fetch config timestamp first
   142  		if lastFetchTime, ok := c.lastFetchConfigTime[gvr]; ok && lastFetchTime.Add(c.ttl).After(time.Now()) {
   143  			return nil
   144  		} else {
   145  			c.lastFetchConfigTime[gvr] = time.Now()
   146  		}
   147  
   148  		schemaGVR := native.ToSchemaGVR(gvr.Group, gvr.Version, gvr.Resource)
   149  		var dynamicClient dynamic.ResourceInterface
   150  		if targetConfig.ConfigNamespace != "" {
   151  			dynamicClient = c.client.DynamicClient.Resource(schemaGVR).Namespace(targetConfig.ConfigNamespace)
   152  		} else {
   153  			dynamicClient = c.client.DynamicClient.Resource(schemaGVR)
   154  		}
   155  
   156  		// todo: emit metrics if fail to get latest dynamic config from APIServer
   157  		klog.Infof("[kcc-sdk] %s targetConfigMeta hash is changed to %s", gvr, targetConfig.Hash)
   158  		conf, err := dynamicClient.Get(ctx, targetConfig.ConfigName, metav1.GetOptions{ResourceVersion: "0"})
   159  		if err != nil {
   160  			return err
   161  		}
   162  
   163  		c.configCache[gvr] = configCache{
   164  			targetConfigHash:    targetConfig.Hash,
   165  			targetConfigContent: util.ToKCCTargetResource(conf),
   166  		}
   167  
   168  		klog.Infof("[kcc-sdk] %s config cache has been updated to %v", gvr.String(), conf)
   169  	}
   170  
   171  	return nil
   172  }