github.com/oam-dev/kubevela@v1.9.11/pkg/addon/cache.go (about)

     1  /*
     2  Copyright 2021 The KubeVela 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 addon
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	"k8s.io/klog/v2"
    27  
    28  	"github.com/oam-dev/kubevela/pkg/utils"
    29  	"github.com/oam-dev/kubevela/pkg/utils/common"
    30  )
    31  
    32  // We have three addon layer here
    33  // 1. List metadata for all file path of one registry
    34  // 2. UIData: Read file content that including README.md and other necessary things being used in UI apiserver
    35  // 3. InstallPackage: All file content that used to be installation
    36  
    37  // Cache package only cache for 1 and 2, we don't cache InstallPackage, and it only read for real installation
    38  type Cache struct {
    39  
    40  	// uiData caches all the decoded UIData addons
    41  	// the key in the map is the registry name
    42  	uiData map[string][]*UIData
    43  
    44  	// registryMeta caches the addon metadata of every registry
    45  	// the key in the map is the registry name
    46  	registryMeta map[string]map[string]SourceMeta
    47  
    48  	registry map[string]Registry
    49  
    50  	versionedUIData map[string]map[string]*UIData
    51  
    52  	mutex *sync.RWMutex
    53  
    54  	ds RegistryDataStore
    55  }
    56  
    57  // NewCache will build a new cache instance
    58  func NewCache(ds RegistryDataStore) *Cache {
    59  	return &Cache{
    60  		uiData:          make(map[string][]*UIData),
    61  		registryMeta:    make(map[string]map[string]SourceMeta),
    62  		registry:        make(map[string]Registry),
    63  		versionedUIData: make(map[string]map[string]*UIData),
    64  		mutex:           new(sync.RWMutex),
    65  		ds:              ds,
    66  	}
    67  }
    68  
    69  // DiscoverAndRefreshLoop will run a loop to automatically discovery and refresh addons from registry
    70  func (u *Cache) DiscoverAndRefreshLoop(ctx context.Context, cacheTime time.Duration) {
    71  	ticker := time.NewTicker(cacheTime)
    72  	defer ticker.Stop()
    73  
    74  	// This is infinite loop, we can receive a channel for close
    75  	for {
    76  		select {
    77  		case <-ticker.C:
    78  			u.discoverAndRefreshRegistry()
    79  		case <-ctx.Done():
    80  			return
    81  		}
    82  	}
    83  }
    84  
    85  // ListAddonMeta will list metadata from registry, if cache not found, it will find from source
    86  func (u *Cache) ListAddonMeta(r Registry) (map[string]SourceMeta, error) {
    87  	registryMeta := u.getCachedAddonMeta(r.Name)
    88  	if registryMeta == nil {
    89  		return r.ListAddonMeta()
    90  	}
    91  	return registryMeta, nil
    92  }
    93  
    94  // GetUIData get addon data for UI display from cache, if cache not found, it will find from source
    95  func (u *Cache) GetUIData(r Registry, addonName, version string) (*UIData, error) {
    96  	addon := u.getCachedUIData(r, addonName, version)
    97  	if addon != nil {
    98  		return addon, nil
    99  	}
   100  	var err error
   101  	if !IsVersionRegistry(r) {
   102  		registryMeta, err := u.ListAddonMeta(r)
   103  		if err != nil {
   104  			return nil, err
   105  		}
   106  		meta, ok := registryMeta[addonName]
   107  		if !ok {
   108  			return nil, ErrNotExist
   109  		}
   110  		addon, err = r.GetUIData(&meta, UIMetaOptions)
   111  		if err != nil {
   112  			return nil, err
   113  		}
   114  	} else {
   115  		versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL, &common.HTTPOption{
   116  			Username:        r.Helm.Username,
   117  			Password:        r.Helm.Password,
   118  			InsecureSkipTLS: r.Helm.InsecureSkipTLS,
   119  		})
   120  		addon, err = versionedRegistry.GetAddonUIData(context.Background(), addonName, version)
   121  		if err != nil {
   122  			klog.Errorf("fail to get addons from registry %s for cache updating, %v", utils.Sanitize(r.Name), err)
   123  			return nil, err
   124  		}
   125  	}
   126  
   127  	return addon, nil
   128  }
   129  
   130  // ListUIData will always list UIData from cache first, if not exist, read from source.
   131  func (u *Cache) ListUIData(r Registry) ([]*UIData, error) {
   132  	var err error
   133  	var listAddons []*UIData
   134  	if !IsVersionRegistry(r) {
   135  		listAddons = u.listCachedUIData(r.Name)
   136  		if listAddons != nil {
   137  			return listAddons, nil
   138  		}
   139  		listAddons, err = u.listUIDataAndCache(r)
   140  		if err != nil {
   141  			return nil, err
   142  		}
   143  	} else {
   144  		listAddons = u.listVersionRegistryCachedUIData(r.Name)
   145  		if listAddons != nil {
   146  			return listAddons, nil
   147  		}
   148  		listAddons, err = u.listVersionRegistryUIDataAndCache(r)
   149  		if err != nil {
   150  			return nil, err
   151  		}
   152  	}
   153  
   154  	return listAddons, nil
   155  }
   156  
   157  func (u *Cache) getCachedUIData(registry Registry, addonName, version string) *UIData {
   158  	if !IsVersionRegistry(registry) {
   159  		addons := u.listCachedUIData(registry.Name)
   160  		for _, a := range addons {
   161  			if a.Name == addonName {
   162  				return a
   163  			}
   164  		}
   165  	} else {
   166  		if len(version) == 0 {
   167  			version = "latest"
   168  		}
   169  		return u.versionedUIData[registry.Name][fmt.Sprintf("%s-%s", addonName, version)]
   170  	}
   171  	return nil
   172  }
   173  
   174  // listCachedUIData will get cached addons from specified registry in cache
   175  func (u *Cache) listCachedUIData(name string) []*UIData {
   176  	if u == nil {
   177  		return nil
   178  	}
   179  	u.mutex.RLock()
   180  	defer u.mutex.RUnlock()
   181  	d, ok := u.uiData[name]
   182  	if !ok {
   183  		return nil
   184  	}
   185  	return d
   186  }
   187  
   188  // listVersionRegistryCachedUIData will get cached addons from specified VersionRegistry in cache
   189  func (u *Cache) listVersionRegistryCachedUIData(name string) []*UIData {
   190  	if u == nil {
   191  		return nil
   192  	}
   193  	u.mutex.RLock()
   194  	defer u.mutex.RUnlock()
   195  	d, ok := u.versionedUIData[name]
   196  	if !ok {
   197  		return nil
   198  	}
   199  	var uiDatas []*UIData
   200  	for version, uiData := range d {
   201  		if !strings.Contains(version, "-latest") {
   202  			uiDatas = append(uiDatas, uiData)
   203  		}
   204  	}
   205  
   206  	return uiDatas
   207  }
   208  
   209  // getCachedAddonMeta will get cached registry meta from specified registry in cache
   210  func (u *Cache) getCachedAddonMeta(name string) map[string]SourceMeta {
   211  	if u == nil {
   212  		return nil
   213  	}
   214  	u.mutex.RLock()
   215  	defer u.mutex.RUnlock()
   216  	d, ok := u.registryMeta[name]
   217  	if !ok {
   218  		return nil
   219  	}
   220  	return d
   221  }
   222  
   223  func (u *Cache) putAddonUIData2Cache(name string, addons []*UIData) {
   224  	if u == nil {
   225  		return
   226  	}
   227  
   228  	u.mutex.Lock()
   229  	defer u.mutex.Unlock()
   230  	u.uiData[name] = addons
   231  }
   232  
   233  func (u *Cache) putAddonMeta2Cache(name string, addonMeta map[string]SourceMeta) {
   234  	if u == nil {
   235  		return
   236  	}
   237  
   238  	u.mutex.Lock()
   239  	defer u.mutex.Unlock()
   240  	u.registryMeta[name] = addonMeta
   241  }
   242  
   243  func (u *Cache) putRegistry2Cache(registry []Registry) {
   244  	if u == nil {
   245  		return
   246  	}
   247  
   248  	u.mutex.Lock()
   249  	defer u.mutex.Unlock()
   250  	for k := range u.registry {
   251  		var found = false
   252  		for _, r := range registry {
   253  			if r.Name == k {
   254  				found = true
   255  				break
   256  			}
   257  		}
   258  		// clean deleted registry
   259  		if !found {
   260  			delete(u.registry, k)
   261  			delete(u.registryMeta, k)
   262  			delete(u.uiData, k)
   263  		}
   264  	}
   265  	for _, r := range registry {
   266  		u.registry[r.Name] = r
   267  	}
   268  }
   269  
   270  func (u *Cache) putVersionedUIData2Cache(registryName, addonName, version string, uiData *UIData) {
   271  	if u == nil {
   272  		return
   273  	}
   274  
   275  	u.mutex.Lock()
   276  	defer u.mutex.Unlock()
   277  
   278  	if u.versionedUIData[registryName] == nil {
   279  		u.versionedUIData[registryName] = make(map[string]*UIData)
   280  	}
   281  	u.versionedUIData[registryName][fmt.Sprintf("%s-%s", addonName, version)] = uiData
   282  }
   283  
   284  func (u *Cache) discoverAndRefreshRegistry() {
   285  	registries, err := u.ds.ListRegistries(context.Background())
   286  	if err != nil {
   287  		klog.Errorf("fail to get registry %v", err)
   288  		return
   289  	}
   290  	u.putRegistry2Cache(registries)
   291  
   292  	for _, r := range registries {
   293  		if !IsVersionRegistry(r) {
   294  			_, err = u.listUIDataAndCache(r)
   295  			if err != nil {
   296  				continue
   297  			}
   298  		} else {
   299  			_, err = u.listVersionRegistryUIDataAndCache(r)
   300  			if err != nil {
   301  				continue
   302  			}
   303  		}
   304  	}
   305  }
   306  
   307  func (u *Cache) listUIDataAndCache(r Registry) ([]*UIData, error) {
   308  	registryMeta, err := r.ListAddonMeta()
   309  	if err != nil {
   310  		klog.Errorf("fail to list registry %s metadata,  %v", r.Name, err)
   311  		return nil, err
   312  	}
   313  	u.putAddonMeta2Cache(r.Name, registryMeta)
   314  	uiData, err := r.ListUIData(registryMeta, UIMetaOptions)
   315  	if err != nil {
   316  		klog.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
   317  		return nil, err
   318  	}
   319  	u.putAddonUIData2Cache(r.Name, uiData)
   320  	return uiData, nil
   321  }
   322  
   323  func (u *Cache) listVersionRegistryUIDataAndCache(r Registry) ([]*UIData, error) {
   324  	versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL, &common.HTTPOption{
   325  		Username:        r.Helm.Username,
   326  		Password:        r.Helm.Password,
   327  		InsecureSkipTLS: r.Helm.InsecureSkipTLS,
   328  	})
   329  	uiDatas, err := versionedRegistry.ListAddon()
   330  	if err != nil {
   331  		klog.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err)
   332  		return nil, err
   333  	}
   334  	for _, addon := range uiDatas {
   335  		uiData, err := versionedRegistry.GetAddonUIData(context.Background(), addon.Name, addon.Version)
   336  		if err != nil {
   337  			klog.Errorf("fail to get addon from versioned registry %s, addon %s version %s for cache updating, %v", r.Name, addon.Name, addon.Version, err)
   338  			continue
   339  		}
   340  		// identity an addon from helm chart structure
   341  		if uiData.Name == "" {
   342  			addon.Name = ""
   343  			continue
   344  		}
   345  		u.putVersionedUIData2Cache(r.Name, addon.Name, addon.Version, uiData)
   346  		// we also no version key, if use get addonUIData without version will return this vale as latest data.
   347  		u.putVersionedUIData2Cache(r.Name, addon.Name, "latest", uiData)
   348  	}
   349  	// delete the addon which has been deleted from the addonRegistryCache
   350  	if addonUIData, ok := u.versionedUIData[r.Name]; ok {
   351  		for k := range addonUIData {
   352  			lastInd := strings.LastIndex(k, "-")
   353  			var needDelete = true
   354  			for _, addon := range uiDatas {
   355  				if k[:lastInd] == addon.Name {
   356  					needDelete = false
   357  					break
   358  				}
   359  			}
   360  			if needDelete {
   361  				delete(addonUIData, k)
   362  			}
   363  		}
   364  	}
   365  	return uiDatas, nil
   366  }