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  }