Использование пакета client-go Kubernetes

Кэширование — одна из важных концепций, используемых в любой программе для быстрого доступа к часто используемым данным.

В этой статье давайте разберемся, насколько легко мы можем использовать методы кэширования, доступные в пакете client-go Kubernetes.

Для начала рассмотрим простой сценарий, в котором мы хотим сохранить ключ и значение в кеше. Полная версия программы доступна в моем репозитории Github.

// cacheTTL is the duration of time to hold the key in cache
const cacheTTL = 20 * time.Second

// keyValue contains the fields to store the key and value
type keyValue struct {
   key   string
   value string
}

func main() {
   cacheStore := cache.NewTTLStore(cacheKeyFunc, cacheTTL)
}
// cacheKeyFunc defines the key function required in TTLStore.
func cacheKeyFunc(obj interface{}) (string, error) {
   return obj.(keyValue).key, nil
}

В приведенном выше фрагменте кода в функции main мы инициализируем хранилище кеша следующим образом:

cacheKeyFunc: указывает, что использовать в качестве ключа для хранения предоставленного пользователем объекта в кеше.

cacheTTL: Время хранения ключа в кэше.

После завершения базовой инициализации мы можем сохранить значение в кеше с помощью следующих функций:

func addToCache(cacheStore cache.Store, object keyValue) error {
   err := cacheStore.Add(object)
   if err != nil {
      klog.Errorf("failed to add key value to cache error", err)
      return err
   }
   return nil
}

func fetchFromCache(cacheStore cache.Store, key string) (string, error) {
   obj, exists, err := cacheStore.GetByKey(key)
   if err != nil {
      klog.Errorf("failed to add key value to cache error", err)
      return "", err
   }
   if !exists {
      klog.Errorf("object does not exist in the cache")
      return "", nil
   }
   return obj.(keyValue).value, nil
}

func deleteFromCache(cacheStore cache.Store, object keyValue) error {
   return cacheStore.Delete(object)
}

В приведенном выше фрагменте кода мы используем методы cachestore.

Добавитьдля сохранения значения в кеше, GetByKeyдля извлечения значения из предоставленного ключа и "Удалить" для удаления значения из кеша.

При выборке нам нужно убедиться, что мы проверяем, существует ли значение для требуемого ключа в кеше, его может не быть в кеше в случае, если срок его действия истек или он никогда не добавлялся.

Помимо методов Add, GetByKey и Delete, в cache store есть множество других методов, таких как Get, Update, List и т. д. — все методы можно увидеть здесь.

Наконец, основная функция выглядит так

func main() {
   cacheStore := cache.NewTTLStore(cacheKeyFunc, cacheTTL)

   testKey := "myKey"
   key := keyValue{
      key:   testKey,
      value: "myValue",
   }

   klog.Infof("adding the key %v to cache", key)

   err := addToCache(cacheStore, key)
   if err != nil {
      klog.Fatalf("failed to add the key %v to cache error %v", key, err)
   }

   klog.Infof("fetching the value for key: %s from cache", testKey)

   value, err := fetchFromCache(cacheStore, "myKey")
   if err != nil {
      klog.Fatalf("failed to fetch value for key %S from cache error %v", testKey, err)
   }
   if value == "" {
      klog.Fatalf("the value for key %s is empty", testKey)
   }

   klog.Infof("successfully fetched the value for key %s from cache value: %s", testKey, value)

   klog.Infof("deleting the key %s from cache", testKey)
   err = deleteFromCache(cacheStore, key)
   if err != nil {
      klog.Fatalf("failed to delete key %s from cache error %v", testKey, err)
   }
}

Наша работа не будет полностью завершена, пока мы не добавим модульный тест для приведенного выше кода. Давайте добавим модульные тесты для функций addToCache и fetchFromCache.

func TestAddToCache(t *testing.T) {

   cacheStore := cache.NewTTLStore(cacheKeyFunc, cacheTTL)
   testKey := keyValue{
      key:   "testKey",
      value: "testValue",
   }
   err := addToCache(cacheStore, testKey)
   if err != nil {
      t.Fatalf("expecting error to be nil but got err %v", err)
   }
}

func TestFetchFromCache(t *testing.T) {

   defaultCacheStoreFunc := func() cache.Store {
      return cache.NewTTLStore(cacheKeyFunc, cacheTTL)
   }
   testKey := keyValue{
      key:   "testKey",
      value: "testValue",
   }

   testCases := []struct {
      name          string
      expectedError error
      expectedValue string
      keyName       string
      sleep         bool
      cacheStore    func() cache.Store
   }{
      {
         name:          "exists in cache",
         expectedValue: "testValue",
         keyName:       "testKey",
         cacheStore:    defaultCacheStoreFunc,
      },
      {
         name:          "not exists in cache",
         expectedValue: "",
         keyName:       "notTestKey",
         cacheStore:    defaultCacheStoreFunc,
      },
      {
         name:          "key in cache expired",
         expectedValue: "",
         keyName:       "testKey",
         cacheStore: func() cache.Store {
            return cache.NewTTLStore(cacheKeyFunc, time.Millisecond)
         },
      },
   }
   for _, tc := range testCases {
      t.Run(tc.name, func(t *testing.T) {
         gs := NewWithT(t)
         cacheStore := tc.cacheStore()
         err := addToCache(cacheStore, testKey)
         if err != nil {
            t.Fatalf("failed to add key to cache error %v", err)
         }
         if tc.sleep {
            time.Sleep(time.Second)
         }
         value, err := fetchFromCache(cacheStore, keyValue{key: tc.keyName})
         if err != nil {
            if tc.expectedError != nil {
               gs.Expect(err).To(HaveOccurred())
               gs.Expect(err.Error()).To(Equal(tc.expectedError.Error()))
            } else {
               gs.Expect(err).ToNot(HaveOccurred())
            }
         }
         gs.Expect(value).To(Equal(tc.expectedValue))
      })
   }
}

Полную программу и тесты можно найти здесь, в моем репозитории GitHub.

Рекомендации

  1. Юнит-тестирование на языке го
  2. Написание пользовательских контроллеров и вебхуков Kubernetes