1 /* 2 Copyright 2019 The Kubernetes 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 /* 18 Package cache implements data structures used by the kubelet plugin manager to 19 keep track of registered plugins. 20 */ 21 package cache 22 23 import ( 24 "fmt" 25 "sync" 26 "time" 27 28 "k8s.io/klog/v2" 29 ) 30 31 // ActualStateOfWorld defines a set of thread-safe operations for the kubelet 32 // plugin manager's actual state of the world cache. 33 // This cache contains a map of socket file path to plugin information of 34 // all plugins attached to this node. 35 type ActualStateOfWorld interface { 36 37 // GetRegisteredPlugins generates and returns a list of plugins 38 // that are successfully registered plugins in the current actual state of world. 39 GetRegisteredPlugins() []PluginInfo 40 41 // AddPlugin add the given plugin in the cache. 42 // An error will be returned if socketPath of the PluginInfo object is empty. 43 // Note that this is different from desired world cache's AddOrUpdatePlugin 44 // because for the actual state of world cache, there won't be a scenario where 45 // we need to update an existing plugin if the timestamps don't match. This is 46 // because the plugin should have been unregistered in the reconciler and therefore 47 // removed from the actual state of world cache first before adding it back into 48 // the actual state of world cache again with the new timestamp 49 AddPlugin(pluginInfo PluginInfo) error 50 51 // RemovePlugin deletes the plugin with the given socket path from the actual 52 // state of world. 53 // If a plugin does not exist with the given socket path, this is a no-op. 54 RemovePlugin(socketPath string) 55 56 // PluginExists checks if the given plugin exists in the current actual 57 // state of world cache with the correct timestamp 58 PluginExistsWithCorrectTimestamp(pluginInfo PluginInfo) bool 59 } 60 61 // NewActualStateOfWorld returns a new instance of ActualStateOfWorld 62 func NewActualStateOfWorld() ActualStateOfWorld { 63 return &actualStateOfWorld{ 64 socketFileToInfo: make(map[string]PluginInfo), 65 } 66 } 67 68 type actualStateOfWorld struct { 69 70 // socketFileToInfo is a map containing the set of successfully registered plugins 71 // The keys are plugin socket file paths. The values are PluginInfo objects 72 socketFileToInfo map[string]PluginInfo 73 sync.RWMutex 74 } 75 76 var _ ActualStateOfWorld = &actualStateOfWorld{} 77 78 // PluginInfo holds information of a plugin 79 type PluginInfo struct { 80 SocketPath string 81 Timestamp time.Time 82 Handler PluginHandler 83 Name string 84 } 85 86 func (asw *actualStateOfWorld) AddPlugin(pluginInfo PluginInfo) error { 87 asw.Lock() 88 defer asw.Unlock() 89 90 if pluginInfo.SocketPath == "" { 91 return fmt.Errorf("socket path is empty") 92 } 93 if _, ok := asw.socketFileToInfo[pluginInfo.SocketPath]; ok { 94 klog.V(2).InfoS("Plugin exists in actual state cache", "path", pluginInfo.SocketPath) 95 } 96 asw.socketFileToInfo[pluginInfo.SocketPath] = pluginInfo 97 return nil 98 } 99 100 func (asw *actualStateOfWorld) RemovePlugin(socketPath string) { 101 asw.Lock() 102 defer asw.Unlock() 103 104 delete(asw.socketFileToInfo, socketPath) 105 } 106 107 func (asw *actualStateOfWorld) GetRegisteredPlugins() []PluginInfo { 108 asw.RLock() 109 defer asw.RUnlock() 110 111 currentPlugins := []PluginInfo{} 112 for _, pluginInfo := range asw.socketFileToInfo { 113 currentPlugins = append(currentPlugins, pluginInfo) 114 } 115 return currentPlugins 116 } 117 118 func (asw *actualStateOfWorld) PluginExistsWithCorrectTimestamp(pluginInfo PluginInfo) bool { 119 asw.RLock() 120 defer asw.RUnlock() 121 122 // We need to check both if the socket file path exists, and the timestamp 123 // matches the given plugin (from the desired state cache) timestamp 124 actualStatePlugin, exists := asw.socketFileToInfo[pluginInfo.SocketPath] 125 return exists && (actualStatePlugin.Timestamp == pluginInfo.Timestamp) 126 } 127