k8s.io/perf-tests/clusterloader2@v0.0.0-20240304094227-64bdb12da87e/pkg/framework/framework.go (about) 1 /* 2 Copyright 2018 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 framework 18 19 import ( 20 "fmt" 21 "io/fs" 22 "regexp" 23 "sync" 24 "time" 25 26 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 27 "k8s.io/apimachinery/pkg/runtime/schema" 28 "k8s.io/apimachinery/pkg/util/wait" 29 "k8s.io/klog/v2" 30 "k8s.io/perf-tests/clusterloader2/pkg/config" 31 "k8s.io/perf-tests/clusterloader2/pkg/errors" 32 "k8s.io/perf-tests/clusterloader2/pkg/framework/client" 33 frconfig "k8s.io/perf-tests/clusterloader2/pkg/framework/config" 34 35 "k8s.io/client-go/discovery" 36 restclient "k8s.io/client-go/rest" 37 38 // ensure auth plugins are loaded 39 _ "k8s.io/client-go/plugin/pkg/client/auth" 40 ) 41 42 var namespaceID = regexp.MustCompile(`^test-[a-z0-9]+-[0-9]+$`) 43 44 // Framework allows for interacting with Kubernetes cluster via 45 // official Kubernetes client. 46 type Framework struct { 47 automanagedNamespacePrefix string 48 mux sync.Mutex 49 // automanagedNamespaces stores values as infromation if should the namespace be deleted 50 automanagedNamespaces map[string]bool 51 clientSets *MultiClientSet 52 dynamicClients *MultiDynamicClient 53 clusterConfig *config.ClusterConfig 54 restClientConfig *restclient.Config 55 discoveryClient *discovery.DiscoveryClient 56 } 57 58 // NewFramework creates new framework based on given clusterConfig. 59 func NewFramework(clusterConfig *config.ClusterConfig, clientsNumber int) (*Framework, error) { 60 return newFramework(clusterConfig, clientsNumber, clusterConfig.KubeConfigPath) 61 } 62 63 // NewRootFramework creates framework for the root cluster. 64 // For clusters other than kubemark there is no difference between NewRootFramework and NewFramework. 65 func NewRootFramework(clusterConfig *config.ClusterConfig, clientsNumber int) (*Framework, error) { 66 kubeConfigPath := clusterConfig.KubeConfigPath 67 if override := clusterConfig.Provider.GetConfig().RootFrameworkKubeConfigOverride(); override != "" { 68 kubeConfigPath = override 69 } 70 return newFramework(clusterConfig, clientsNumber, kubeConfigPath) 71 } 72 73 func newFramework(clusterConfig *config.ClusterConfig, clientsNumber int, kubeConfigPath string) (*Framework, error) { 74 klog.Infof("Creating framework with %d clients and %q kubeconfig.", clientsNumber, kubeConfigPath) 75 var err error 76 f := Framework{ 77 automanagedNamespaces: map[string]bool{}, 78 clusterConfig: clusterConfig, 79 } 80 if f.clientSets, err = NewMultiClientSet(kubeConfigPath, clientsNumber); err != nil { 81 return nil, fmt.Errorf("multi client set creation error: %v", err) 82 } 83 if f.dynamicClients, err = NewMultiDynamicClient(kubeConfigPath, clientsNumber); err != nil { 84 return nil, fmt.Errorf("multi dynamic client creation error: %v", err) 85 } 86 87 if f.restClientConfig, err = frconfig.GetConfig(kubeConfigPath); err != nil { 88 return nil, fmt.Errorf("rest client creation error: %v", err) 89 } 90 91 if f.discoveryClient, err = discovery.NewDiscoveryClientForConfig(f.restClientConfig); err != nil { 92 return nil, fmt.Errorf("discovery client creation error: %v", err) 93 } 94 95 return &f, nil 96 } 97 98 // GetAutomanagedNamespacePrefix returns automanaged namespace prefix. 99 func (f *Framework) GetAutomanagedNamespacePrefix() string { 100 return f.automanagedNamespacePrefix 101 } 102 103 // SetAutomanagedNamespacePrefix sets automanaged namespace prefix. 104 func (f *Framework) SetAutomanagedNamespacePrefix(nsName string) { 105 f.automanagedNamespacePrefix = nsName 106 } 107 108 // GetClientSets returns clientSet clients. 109 func (f *Framework) GetClientSets() *MultiClientSet { 110 return f.clientSets 111 } 112 113 // GetDynamicClients returns dynamic clients. 114 func (f *Framework) GetDynamicClients() *MultiDynamicClient { 115 return f.dynamicClients 116 } 117 118 func (f *Framework) GetRestClient() *restclient.Config { 119 return f.restClientConfig 120 } 121 122 // GetClusterConfig returns cluster config. 123 func (f *Framework) GetClusterConfig() *config.ClusterConfig { 124 return f.clusterConfig 125 } 126 127 func (f *Framework) GetDiscoveryClient() *discovery.DiscoveryClient { 128 return f.discoveryClient 129 } 130 131 // CreateAutomanagedNamespaces creates automanged namespaces. 132 func (f *Framework) CreateAutomanagedNamespaces(namespaceCount int, allowExistingNamespaces bool, deleteAutomanagedNamespaces bool) error { 133 f.mux.Lock() 134 defer f.mux.Unlock() 135 // get all pre-created namespaces and store in a hash set 136 namespacesList, err := client.ListNamespaces(f.clientSets.GetClient()) 137 if err != nil { 138 return err 139 } 140 existingNamespaceSet := make(map[string]bool) 141 for _, ns := range namespacesList { 142 existingNamespaceSet[ns.Name] = true 143 } 144 145 for i := 1; i <= namespaceCount; i++ { 146 name := fmt.Sprintf("%v-%d", f.automanagedNamespacePrefix, i) 147 if _, created := existingNamespaceSet[name]; !created { 148 if err := client.CreateNamespace(f.clientSets.GetClient(), name); err != nil { 149 return err 150 } 151 } else { 152 if !allowExistingNamespaces { 153 return fmt.Errorf("automanaged namespace %s already created", name) 154 } 155 } 156 f.automanagedNamespaces[name] = deleteAutomanagedNamespaces 157 } 158 return nil 159 } 160 161 // ListAutomanagedNamespaces returns all existing automanged namespace names. 162 func (f *Framework) ListAutomanagedNamespaces() ([]string, []string, error) { 163 var automanagedNamespacesCurrentPrefixList, staleNamespaces []string 164 namespacesList, err := client.ListNamespaces(f.clientSets.GetClient()) 165 if err != nil { 166 return automanagedNamespacesCurrentPrefixList, staleNamespaces, err 167 } 168 for _, namespace := range namespacesList { 169 matched, err := f.isAutomanagedNamespaceCurrentPrefix(namespace.Name) 170 if err != nil { 171 return automanagedNamespacesCurrentPrefixList, staleNamespaces, err 172 } 173 if matched { 174 automanagedNamespacesCurrentPrefixList = append(automanagedNamespacesCurrentPrefixList, namespace.Name) 175 } else { 176 // check further whether the namespace is a automanaged namespace created in previous test execution. 177 // this could happen when the execution is aborted abornamlly, and the resource is not able to be 178 // clean up. 179 matched := f.isStaleAutomanagedNamespace(namespace.Name) 180 if matched { 181 staleNamespaces = append(staleNamespaces, namespace.Name) 182 } 183 } 184 } 185 return automanagedNamespacesCurrentPrefixList, staleNamespaces, nil 186 } 187 188 func (f *Framework) deleteNamespace(namespace string, timeout time.Duration) error { 189 clientSet := f.clientSets.GetClient() 190 if err := client.DeleteNamespace(clientSet, namespace); err != nil { 191 return err 192 } 193 if err := client.WaitForDeleteNamespace(clientSet, namespace, timeout); err != nil { 194 return err 195 } 196 f.removeAutomanagedNamespace(namespace) 197 return nil 198 } 199 200 // DeleteAutomanagedNamespaces deletes all automanged namespaces. 201 func (f *Framework) DeleteAutomanagedNamespaces(timeout time.Duration) *errors.ErrorList { 202 var wg wait.Group 203 errList := errors.NewErrorList() 204 for namespace, shouldBeDeleted := range f.getAutomanagedNamespaces() { 205 namespace := namespace 206 if shouldBeDeleted { 207 wg.Start(func() { 208 if err := f.deleteNamespace(namespace, timeout); err != nil { 209 errList.Append(err) 210 return 211 } 212 }) 213 } 214 } 215 wg.Wait() 216 return errList 217 } 218 219 func (f *Framework) removeAutomanagedNamespace(namespace string) { 220 f.mux.Lock() 221 defer f.mux.Unlock() 222 delete(f.automanagedNamespaces, namespace) 223 } 224 225 func (f *Framework) getAutomanagedNamespaces() map[string]bool { 226 f.mux.Lock() 227 defer f.mux.Unlock() 228 m := map[string]bool{} 229 for k, v := range f.automanagedNamespaces { 230 m[k] = v 231 } 232 return m 233 } 234 235 // DeleteNamespaces deletes the list of namespaces. 236 func (f *Framework) DeleteNamespaces(namespaces []string, timeout time.Duration) *errors.ErrorList { 237 var wg wait.Group 238 errList := errors.NewErrorList() 239 for _, namespace := range namespaces { 240 namespace := namespace 241 wg.Start(func() { 242 if err := f.deleteNamespace(namespace, timeout); err != nil { 243 errList.Append(err) 244 return 245 } 246 }) 247 } 248 wg.Wait() 249 return errList 250 } 251 252 // CreateObject creates object base on given object description. 253 func (f *Framework) CreateObject(namespace string, name string, obj *unstructured.Unstructured, options ...*client.APICallOptions) error { 254 return client.CreateObject(f.dynamicClients.GetClient(), namespace, name, obj, options...) 255 } 256 257 // PatchObject updates object (using patch) with given name using given object description. 258 func (f *Framework) PatchObject(namespace string, name string, obj *unstructured.Unstructured, options ...*client.APICallOptions) error { 259 return client.PatchObject(f.dynamicClients.GetClient(), namespace, name, obj) 260 } 261 262 // DeleteObject deletes object with given name and group-version-kind. 263 func (f *Framework) DeleteObject(gvk schema.GroupVersionKind, namespace string, name string, options ...*client.APICallOptions) error { 264 return client.DeleteObject(f.dynamicClients.GetClient(), gvk, namespace, name) 265 } 266 267 // GetObject retrieves object with given name and group-version-kind. 268 func (f *Framework) GetObject(gvk schema.GroupVersionKind, namespace string, name string, options ...*client.APICallOptions) (*unstructured.Unstructured, error) { 269 return client.GetObject(f.dynamicClients.GetClient(), gvk, namespace, name) 270 } 271 272 // ApplyTemplatedManifests finds and applies all manifest template files matching the provided 273 // manifestGlob pattern. It substitutes the template placeholders using the templateMapping map. 274 func (f *Framework) ApplyTemplatedManifests(fsys fs.FS, manifestGlob string, templateMapping map[string]interface{}, options ...*client.APICallOptions) error { 275 // TODO(mm4tt): Consider using the out-of-the-box "kubectl create -f". 276 klog.Infof("Applying templates for %q", manifestGlob) 277 278 templateProvider := config.NewTemplateProvider(fsys) 279 manifests, err := fs.Glob(fsys, manifestGlob) 280 if err != nil { 281 return err 282 } 283 if manifests == nil { 284 klog.Warningf("There is no matching file for pattern %v.\n", manifestGlob) 285 } 286 for _, manifest := range manifests { 287 klog.V(1).Infof("Applying %s\n", manifest) 288 obj, err := templateProvider.TemplateToObject(manifest, templateMapping) 289 if err != nil { 290 if err == config.ErrorEmptyFile { 291 klog.Warningf("Skipping empty manifest %s", manifest) 292 continue 293 } 294 return fmt.Errorf("TemplateToObject error: %+v", err) 295 } 296 objList := []unstructured.Unstructured{*obj} 297 if obj.IsList() { 298 list, err := obj.ToList() 299 if err != nil { 300 return err 301 } 302 objList = list.Items 303 } 304 for _, item := range objList { 305 if err := f.CreateObject(item.GetNamespace(), item.GetName(), &item, options...); err != nil { 306 return fmt.Errorf("error while applying (%s): %v", manifest, err) 307 } 308 } 309 310 } 311 return nil 312 } 313 314 func (f *Framework) isAutomanagedNamespaceCurrentPrefix(name string) (bool, error) { 315 return regexp.MatchString(f.automanagedNamespacePrefix+"-[1-9][0-9]*", name) 316 } 317 318 func (f *Framework) isStaleAutomanagedNamespace(name string) bool { 319 if namespaceID.MatchString(name) { 320 _, isFromThisExecution := f.getAutomanagedNamespaces()[name] 321 return !isFromThisExecution 322 } 323 return false 324 }