1/*
2Copyright 2016 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package kubelet
18
19import (
20	"fmt"
21	"sync"
22
23	"github.com/golang/groupcache/lru"
24	"k8s.io/apimachinery/pkg/types"
25	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
26)
27
28// ReasonCache stores the failure reason of the latest container start
29// in a string, keyed by <pod_UID>_<container_name>. The goal is to
30// propagate this reason to the container status. This endeavor is
31// "best-effort" for two reasons:
32//   1. The cache is not persisted.
33//   2. We use an LRU cache to avoid extra garbage collection work. This
34//      means that some entries may be recycled before a pod has been
35//      deleted.
36// TODO(random-liu): Use more reliable cache which could collect garbage of failed pod.
37// TODO(random-liu): Move reason cache to somewhere better.
38type ReasonCache struct {
39	lock  sync.Mutex
40	cache *lru.Cache
41}
42
43// ReasonItem is the cached item in ReasonCache
44type ReasonItem struct {
45	Err     error
46	Message string
47}
48
49// maxReasonCacheEntries is the cache entry number in lru cache. 1000 is a proper number
50// for our 100 pods per node target. If we support more pods per node in the future, we
51// may want to increase the number.
52const maxReasonCacheEntries = 1000
53
54// NewReasonCache creates an instance of 'ReasonCache'.
55func NewReasonCache() *ReasonCache {
56	return &ReasonCache{cache: lru.New(maxReasonCacheEntries)}
57}
58
59func (c *ReasonCache) composeKey(uid types.UID, name string) string {
60	return fmt.Sprintf("%s_%s", uid, name)
61}
62
63// add adds error reason into the cache
64func (c *ReasonCache) add(uid types.UID, name string, reason error, message string) {
65	c.lock.Lock()
66	defer c.lock.Unlock()
67	c.cache.Add(c.composeKey(uid, name), ReasonItem{reason, message})
68}
69
70// Update updates the reason cache with the SyncPodResult. Only SyncResult with
71// StartContainer action will change the cache.
72func (c *ReasonCache) Update(uid types.UID, result kubecontainer.PodSyncResult) {
73	for _, r := range result.SyncResults {
74		if r.Action != kubecontainer.StartContainer {
75			continue
76		}
77		name := r.Target.(string)
78		if r.Error != nil {
79			c.add(uid, name, r.Error, r.Message)
80		} else {
81			c.Remove(uid, name)
82		}
83	}
84}
85
86// Remove removes error reason from the cache
87func (c *ReasonCache) Remove(uid types.UID, name string) {
88	c.lock.Lock()
89	defer c.lock.Unlock()
90	c.cache.Remove(c.composeKey(uid, name))
91}
92
93// Get gets error reason from the cache. The return values are error reason, error message and
94// whether an error reason is found in the cache. If no error reason is found, empty string will
95// be returned for error reason and error message.
96func (c *ReasonCache) Get(uid types.UID, name string) (*ReasonItem, bool) {
97	c.lock.Lock()
98	defer c.lock.Unlock()
99	value, ok := c.cache.Get(c.composeKey(uid, name))
100	if !ok {
101		return nil, false
102	}
103	info := value.(ReasonItem)
104	return &info, true
105}
106