k8s.io/perf-tests/clusterloader2@v0.0.0-20240304094227-64bdb12da87e/pkg/imagepreload/imagepreload.go (about) 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 package imagepreload 18 19 import ( 20 "context" 21 "embed" 22 "strings" 23 "sync" 24 "time" 25 26 v1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/util/wait" 30 "k8s.io/apimachinery/pkg/watch" 31 "k8s.io/client-go/tools/cache" 32 "k8s.io/klog/v2" 33 "k8s.io/perf-tests/clusterloader2/pkg/config" 34 "k8s.io/perf-tests/clusterloader2/pkg/flags" 35 "k8s.io/perf-tests/clusterloader2/pkg/framework" 36 "k8s.io/perf-tests/clusterloader2/pkg/framework/client" 37 "k8s.io/perf-tests/clusterloader2/pkg/measurement/util/informer" 38 "k8s.io/perf-tests/clusterloader2/pkg/measurement/util/runtimeobjects" 39 ) 40 41 const ( 42 informerTimeout = time.Minute 43 manifest = "manifests/daemonset.yaml" 44 namespace = "preload" 45 daemonsetName = "preload" 46 pollingInterval = 5 * time.Second 47 pollingTimeout = 15 * time.Minute 48 ) 49 50 var ( 51 images []string 52 //go:embed manifests 53 manifestsFS embed.FS 54 ) 55 56 func init() { 57 flags.StringSliceEnvVar(&images, "node-preload-images", "NODE_PRELOAD_IMAGES", []string{}, "List of images to preload on each node in the test cluster before executing tests") 58 } 59 60 type controller struct { 61 // lock for controlling access to doneNodes in PreloadImages 62 lock sync.Mutex 63 64 config *config.ClusterLoaderConfig 65 framework *framework.Framework 66 templateMapping map[string]interface{} 67 images []string 68 } 69 70 // Setup ensures every node in cluster preloads given list of images before starting tests. 71 // It does it by creating a Daemonset that call "docker pull" and awaits for Node object to be updated. 72 // As a side-effect of the image preloading, size of Node objects is increased. 73 // 74 // Preloading is skipped in kubemark or if no images have been specified. 75 func Setup(conf *config.ClusterLoaderConfig, f *framework.Framework) error { 76 mapping, err := config.GetMapping(conf, nil) 77 if err != nil { 78 return err 79 } 80 81 ctl := &controller{ 82 config: conf, 83 framework: f, 84 templateMapping: mapping, 85 images: images, 86 } 87 return ctl.PreloadImages() 88 } 89 90 func (c *controller) PreloadImages() error { 91 if len(images) == 0 { 92 klog.Warning("No images specified. Skipping image preloading") 93 return nil 94 } 95 if !c.config.ClusterConfig.Provider.Features().SupportImagePreload { 96 klog.Warningf("Image preloading is disabled in provider: %s", c.config.ClusterConfig.Provider.Name()) 97 return nil 98 } 99 100 kclient := c.framework.GetClientSets().GetClient() 101 102 doneNodes := make(map[string]struct{}) 103 stopCh := make(chan struct{}) 104 defer close(stopCh) 105 106 nodeInformer := informer.NewInformer( 107 &cache.ListWatch{ 108 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 109 return kclient.CoreV1().Nodes().List(context.TODO(), options) 110 }, 111 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 112 return kclient.CoreV1().Nodes().Watch(context.TODO(), options) 113 }, 114 }, 115 func(old, new interface{}) { c.checkNode(doneNodes, old, new) }) 116 if err := informer.StartAndSync(nodeInformer, stopCh, informerTimeout); err != nil { 117 return err 118 } 119 120 klog.V(2).Infof("Creating namespace %s...", namespace) 121 if err := client.CreateNamespace(kclient, namespace); err != nil { 122 return err 123 } 124 125 klog.V(2).Info("Creating daemonset to preload images...") 126 c.templateMapping["Images"] = c.images 127 if err := c.framework.ApplyTemplatedManifests(manifestsFS, manifest, c.templateMapping); err != nil { 128 return err 129 } 130 131 klog.V(2).Infof("Getting %s/%s deamonset size...", namespace, daemonsetName) 132 ds, err := kclient.AppsV1().DaemonSets(namespace).Get(context.TODO(), daemonsetName, metav1.GetOptions{}) 133 if err != nil { 134 return err 135 } 136 size, err := runtimeobjects.GetReplicasFromRuntimeObject(kclient, ds) 137 if err != nil { 138 return err 139 } 140 if err := size.Start(stopCh); err != nil { 141 return err 142 } 143 144 var clusterSize, doneCount int 145 klog.V(2).Infof("Waiting for %d Node objects to be updated...", size.Replicas()) 146 if err := wait.Poll(pollingInterval, pollingTimeout, func() (bool, error) { 147 clusterSize = size.Replicas() 148 doneCount = c.countDone(doneNodes) 149 klog.V(3).Infof("%d out of %d nodes have pulled images", doneCount, clusterSize) 150 return doneCount == clusterSize, nil 151 }); err != nil { 152 klog.Errorf("%d out of %d nodes have pulled images", doneCount, clusterSize) 153 return err 154 } 155 klog.V(2).Info("Waiting... done") 156 157 klog.V(2).Infof("Deleting namespace %s...", namespace) 158 if err := client.DeleteNamespace(kclient, namespace); err != nil { 159 return err 160 } 161 if err := client.WaitForDeleteNamespace(kclient, namespace, client.DefaultNamespaceDeletionTimeout); err != nil { 162 return err 163 } 164 return nil 165 } 166 167 func (c *controller) checkNode(set map[string]struct{}, old, new interface{}) { 168 if new != nil { 169 node := new.(*v1.Node) 170 preloaded := c.hasPreloadedImages(node) 171 c.markDone(set, node.Name, preloaded) 172 return 173 } 174 if old != nil { 175 node := old.(*v1.Node) 176 c.markDone(set, node.Name, false) 177 return 178 } 179 180 } 181 182 func (c *controller) markDone(set map[string]struct{}, node string, done bool) { 183 c.lock.Lock() 184 defer c.lock.Unlock() 185 if done { 186 set[node] = struct{}{} 187 } else { 188 delete(set, node) 189 } 190 } 191 192 func (c *controller) countDone(set map[string]struct{}) int { 193 c.lock.Lock() 194 defer c.lock.Unlock() 195 return len(set) 196 } 197 198 func (c *controller) hasPreloadedImages(node *v1.Node) bool { 199 nodeImages := make([]string, 0, 20) 200 for _, nodeImg := range node.Status.Images { 201 nodeImages = append(nodeImages, nodeImg.Names...) 202 } 203 204 for _, img := range c.images { 205 found := false 206 for _, nodeImg := range nodeImages { 207 found = strings.HasPrefix(nodeImg, img) 208 if found { 209 break 210 } 211 } 212 if !found { 213 return false 214 } 215 } 216 return true 217 }