k8s.io/perf-tests/clusterloader2@v0.0.0-20240304094227-64bdb12da87e/pkg/test/simple_test_executor.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 test 18 19 import ( 20 "fmt" 21 "io/ioutil" 22 "path" 23 "strings" 24 "time" 25 26 "k8s.io/perf-tests/clusterloader2/pkg/measurement" 27 28 "k8s.io/apimachinery/pkg/api/meta" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 31 "k8s.io/apimachinery/pkg/util/sets" 32 "k8s.io/apimachinery/pkg/util/wait" 33 "k8s.io/klog/v2" 34 "k8s.io/perf-tests/clusterloader2/api" 35 "k8s.io/perf-tests/clusterloader2/pkg/config" 36 "k8s.io/perf-tests/clusterloader2/pkg/errors" 37 "k8s.io/perf-tests/clusterloader2/pkg/measurement/util/runtimeobjects" 38 "k8s.io/perf-tests/clusterloader2/pkg/state" 39 "k8s.io/perf-tests/clusterloader2/pkg/util" 40 ) 41 42 const ( 43 baseNamePlaceholder = "BaseName" 44 indexPlaceholder = "Index" 45 namePlaceholder = "Name" 46 namespacePlaceholder = "Namespace" 47 ) 48 49 type simpleExecutor struct{} 50 51 func createSimpleExecutor() Executor { 52 return &simpleExecutor{} 53 } 54 55 // ExecuteTest executes test based on provided configuration. 56 func (ste *simpleExecutor) ExecuteTest(ctx Context, conf *api.Config) *errors.ErrorList { 57 ctx.GetClusterFramework().SetAutomanagedNamespacePrefix(conf.Namespace.Prefix) 58 klog.V(2).Infof("AutomanagedNamespacePrefix: %s", ctx.GetClusterFramework().GetAutomanagedNamespacePrefix()) 59 60 defer cleanupResources(ctx, conf) 61 ctx.GetFactory().Init(conf.TuningSets) 62 63 stopCh := make(chan struct{}) 64 if conf.ChaosMonkey.ExcludedNodes == nil { 65 conf.ChaosMonkey.ExcludedNodes = sets.NewString() 66 } 67 chaosMonkeyWaitGroup, err := ctx.GetChaosMonkey().Init(conf.ChaosMonkey, stopCh) 68 if err != nil { 69 close(stopCh) 70 return errors.NewErrorList(fmt.Errorf("error while creating chaos monkey: %v", err)) 71 } 72 if err := ste.prepareTestNamespaces(ctx, conf); err != nil { 73 return errors.NewErrorList(fmt.Errorf("error while preparing test namespaces: %w", err)) 74 } 75 errList := ste.ExecuteTestSteps(ctx, conf.Steps) 76 close(stopCh) 77 78 if chaosMonkeyWaitGroup != nil { 79 // Wait for the Chaos Monkey subroutine to end 80 klog.V(2).Info("Waiting for the chaos monkey subroutine to end...") 81 chaosMonkeyWaitGroup.Wait() 82 klog.V(2).Info("Chaos monkey ended.") 83 } 84 85 for _, summary := range ctx.GetManager().GetSummaries() { 86 if ctx.GetClusterLoaderConfig().ReportDir == "" { 87 klog.V(2).Infof("%v: %v", summary.SummaryName(), summary.SummaryContent()) 88 } else { 89 testDistinctor := "" 90 if ctx.GetTestScenario().Identifier != "" { 91 testDistinctor = "_" + ctx.GetTestScenario().Identifier 92 } 93 // TODO(krzysied): Remember to keep original filename style for backward compatibility. 94 fileName := strings.Join([]string{summary.SummaryName(), conf.Name + testDistinctor, summary.SummaryTime().Format(time.RFC3339)}, "_") 95 filePath := path.Join(ctx.GetClusterLoaderConfig().ReportDir, strings.Join([]string{fileName, summary.SummaryExt()}, ".")) 96 if err := ioutil.WriteFile(filePath, []byte(summary.SummaryContent()), 0644); err != nil { 97 errList.Append(fmt.Errorf("writing to file %v error: %v", filePath, err)) 98 continue 99 } 100 } 101 } 102 klog.V(2).Infof(ctx.GetChaosMonkey().Summary()) 103 return errList 104 } 105 106 // prepareTestNamespaces prepares k8s namespaces for the test. 107 func (ste *simpleExecutor) prepareTestNamespaces(ctx Context, conf *api.Config) error { 108 automanagedNamespacesCurrentPrefixList, staleNamespaces, err := ctx.GetClusterFramework().ListAutomanagedNamespaces() 109 if err != nil { 110 return fmt.Errorf("automanaged namespaces listing failed: %w", err) 111 } 112 if len(automanagedNamespacesCurrentPrefixList) > 0 && *conf.Namespace.EnableExistingNamespaces == false { 113 return fmt.Errorf("pre-existing automanaged namespaces found") 114 } 115 var deleteStaleNS = *conf.Namespace.DeleteStaleNamespaces 116 if len(staleNamespaces) > 0 && deleteStaleNS { 117 klog.Warning("stale automanaged namespaces found") 118 if errList := ctx.GetClusterFramework().DeleteNamespaces(staleNamespaces, conf.Namespace.DeleteNamespaceTimeout.ToTimeDuration()); !errList.IsEmpty() { 119 klog.Errorf("stale automanaged namespaces cleanup error: %s", errList.String()) 120 } 121 } 122 if err := ctx.GetClusterFramework().CreateAutomanagedNamespaces(int(conf.Namespace.Number), *conf.Namespace.EnableExistingNamespaces, *conf.Namespace.DeleteAutomanagedNamespaces); err != nil { 123 return fmt.Errorf("automanaged namespaces creation failed: %w", err) 124 } 125 return nil 126 } 127 128 // ExecuteTestSteps executes all test steps provided in configuration 129 func (ste *simpleExecutor) ExecuteTestSteps(ctx Context, steps []*api.Step) *errors.ErrorList { 130 errList := errors.NewErrorList() 131 for i, step := range steps { 132 namePrefix := step.Name 133 if namePrefix == "" { 134 namePrefix = "[autogenerated, please name your step in the test config]" 135 } 136 step.Name = fmt.Sprintf("[step: %02d] %s", i+1, namePrefix) 137 if stepErrList := ste.ExecuteStep(ctx, step); !stepErrList.IsEmpty() { 138 errList.Concat(stepErrList) 139 if isErrsCritical(stepErrList) { 140 return errList 141 } 142 } 143 } 144 return errList 145 } 146 147 // ExecuteStep executes single test step based on provided step configuration. 148 func (ste *simpleExecutor) ExecuteStep(ctx Context, step *api.Step) *errors.ErrorList { 149 klog.V(2).Infof("Step %q started", step.Name) 150 var wg wait.Group 151 stepResults := NewStepResult(step.Name) 152 153 // We already have validation so we know that either Measurements or Phases is non-empty. 154 for i := range step.Measurements { 155 currentMeasurement := step.Measurements[i] 156 substepName := fmt.Sprintf("[%02d] - %s", i, currentMeasurement.Identifier) 157 substepID := i 158 wg.Start(func() { 159 errList := measurement.Execute(ctx.GetManager(), currentMeasurement) 160 stepResults.AddSubStepResult(substepName, substepID, errList) 161 }) 162 } 163 for i := range step.Phases { 164 phase := step.Phases[i] 165 wg.Start(func() { 166 errList := ste.ExecutePhase(ctx, phase) 167 stepResults.AddStepError(errList) 168 }) 169 } 170 wg.Wait() 171 klog.V(2).Infof("Step %q ended", step.Name) 172 allErrors := stepResults.GetAllErrors() 173 if !allErrors.IsEmpty() { 174 klog.Warningf("Got errors during step execution: %v", allErrors) 175 } 176 ctx.GetTestReporter().ReportTestStep(stepResults) 177 return allErrors 178 } 179 180 // ExecutePhase executes single test phase based on provided phase configuration. 181 func (ste *simpleExecutor) ExecutePhase(ctx Context, phase *api.Phase) *errors.ErrorList { 182 errList := errors.NewErrorList() 183 nsList := phase.NamespaceList 184 if nsList == nil { 185 nsList = createNamespacesList(ctx, phase.NamespaceRange) 186 } 187 tuningSet, err := ctx.GetFactory().CreateTuningSet(phase.TuningSet) 188 if err != nil { 189 return errors.NewErrorList(fmt.Errorf("tuning set creation error: %v", err)) 190 } 191 192 var actions []func() 193 for namespaceIndex := range nsList { 194 nsName := nsList[namespaceIndex] 195 instancesStates := make([]*state.InstancesState, 0) 196 // Updating state (DesiredReplicaCount) of every object in object bundle. 197 for j := range phase.ObjectBundle { 198 id, err := getIdentifier(ctx, phase.ObjectBundle[j]) 199 if err != nil { 200 errList.Append(err) 201 return errList 202 } 203 instances, exists := ctx.GetState().GetNamespacesState().Get(nsName, id) 204 if !exists { 205 currentReplicaCount, err := getReplicaCountOfNewObject(ctx, nsName, phase.ObjectBundle[j]) 206 if err != nil { 207 errList.Append(err) 208 return errList 209 } 210 instances = &state.InstancesState{ 211 DesiredReplicaCount: 0, 212 CurrentReplicaCount: currentReplicaCount, 213 Object: phase.ObjectBundle[j], 214 } 215 } 216 instances.DesiredReplicaCount = phase.ReplicasPerNamespace 217 ctx.GetState().GetNamespacesState().Set(nsName, id, instances) 218 instancesStates = append(instancesStates, instances) 219 } 220 221 if err := verifyBundleCorrectness(instancesStates); err != nil { 222 klog.Errorf("Skipping phase. Incorrect bundle in phase: %+v", *phase) 223 return errors.NewErrorList(err) 224 } 225 226 if len(instancesStates) == 0 { 227 return nil 228 } 229 230 // Deleting objects with index greater or equal requested replicas per namespace number. 231 // Objects will be deleted in reversed order. 232 for replicaCounter := phase.ReplicasPerNamespace; replicaCounter < instancesStates[0].CurrentReplicaCount; replicaCounter++ { 233 replicaIndex := replicaCounter 234 actions = append(actions, func() { 235 for j := len(phase.ObjectBundle) - 1; j >= 0; j-- { 236 if replicaIndex < instancesStates[j].CurrentReplicaCount { 237 if objectErrList := ste.ExecuteObject(ctx, phase.ObjectBundle[j], nsName, replicaIndex, deleteObject); !objectErrList.IsEmpty() { 238 errList.Concat(objectErrList) 239 } 240 } 241 } 242 }) 243 } 244 245 // Updating objects when desired replicas per namespace equals current replica count. 246 if instancesStates[0].CurrentReplicaCount == phase.ReplicasPerNamespace { 247 for replicaCounter := int32(0); replicaCounter < phase.ReplicasPerNamespace; replicaCounter++ { 248 replicaIndex := replicaCounter 249 actions = append(actions, func() { 250 for j := range phase.ObjectBundle { 251 if objectErrList := ste.ExecuteObject(ctx, phase.ObjectBundle[j], nsName, replicaIndex, patchObject); !objectErrList.IsEmpty() { 252 errList.Concat(objectErrList) 253 // If error then skip this bundle 254 break 255 } 256 } 257 }) 258 } 259 } 260 261 // Adding objects with index greater than current replica count and lesser than desired replicas per namespace. 262 for replicaCounter := instancesStates[0].CurrentReplicaCount; replicaCounter < phase.ReplicasPerNamespace; replicaCounter++ { 263 replicaIndex := replicaCounter 264 actions = append(actions, func() { 265 for j := range phase.ObjectBundle { 266 if objectErrList := ste.ExecuteObject(ctx, phase.ObjectBundle[j], nsName, replicaIndex, createObject); !objectErrList.IsEmpty() { 267 errList.Concat(objectErrList) 268 // If error then skip this bundle 269 break 270 } 271 } 272 }) 273 } 274 275 // Updating state (CurrentReplicaCount) of every object in object bundle. 276 defer func() { 277 for j := range phase.ObjectBundle { 278 id, _ := getIdentifier(ctx, phase.ObjectBundle[j]) 279 instancesStates[j].CurrentReplicaCount = instancesStates[j].DesiredReplicaCount 280 ctx.GetState().GetNamespacesState().Set(nsName, id, instancesStates[j]) 281 } 282 }() 283 284 } 285 tuningSet.Execute(actions) 286 return errList 287 } 288 289 // ExecuteObject executes single test object operation based on provided object configuration. 290 func (ste *simpleExecutor) ExecuteObject(ctx Context, object *api.Object, namespace string, replicaIndex int32, operation OperationType) *errors.ErrorList { 291 objName := fmt.Sprintf("%v-%d", object.Basename, replicaIndex) 292 var err error 293 var obj *unstructured.Unstructured 294 switch operation { 295 case createObject, patchObject: 296 mapping := ctx.GetTemplateMappingCopy() 297 if object.TemplateFillMap != nil { 298 util.CopyMap(object.TemplateFillMap, mapping) 299 } 300 mapping[baseNamePlaceholder] = object.Basename 301 mapping[indexPlaceholder] = replicaIndex 302 mapping[namePlaceholder] = objName 303 mapping[namespacePlaceholder] = namespace 304 obj, err = ctx.GetTemplateProvider().TemplateToObject(object.ObjectTemplatePath, mapping) 305 if err != nil && err != config.ErrorEmptyFile { 306 return errors.NewErrorList(fmt.Errorf("reading template (%v) error: %v", object.ObjectTemplatePath, err)) 307 } 308 case deleteObject: 309 obj, err = ctx.GetTemplateProvider().RawToObject(object.ObjectTemplatePath) 310 if err != nil && err != config.ErrorEmptyFile { 311 return errors.NewErrorList(fmt.Errorf("reading template (%v) for deletion error: %v", object.ObjectTemplatePath, err)) 312 } 313 default: 314 return errors.NewErrorList(fmt.Errorf("unsupported operation %v for namespace %v object %v", operation, namespace, objName)) 315 } 316 errList := errors.NewErrorList() 317 if err == config.ErrorEmptyFile { 318 return errList 319 } 320 gvk := obj.GroupVersionKind() 321 switch operation { 322 case createObject: 323 if err := ctx.GetClusterFramework().CreateObject(namespace, objName, obj); err != nil { 324 errList.Append(fmt.Errorf("namespace %v object %v creation error: %v", namespace, objName, err)) 325 } 326 case patchObject: 327 if err := ctx.GetClusterFramework().PatchObject(namespace, objName, obj); err != nil { 328 errList.Append(fmt.Errorf("namespace %v object %v updating error: %v", namespace, objName, err)) 329 } 330 case deleteObject: 331 if err := ctx.GetClusterFramework().DeleteObject(gvk, namespace, objName); err != nil { 332 errList.Append(fmt.Errorf("namespace %v object %v deletion error: %v", namespace, objName, err)) 333 } 334 } 335 return errList 336 } 337 338 // verifyBundleCorrectness checks if all bundle objects have the same replica count. 339 func verifyBundleCorrectness(instancesStates []*state.InstancesState) error { 340 const uninitialized int32 = -1 341 expectedReplicaCount := uninitialized 342 for j := range instancesStates { 343 if expectedReplicaCount != uninitialized && instancesStates[j].CurrentReplicaCount != expectedReplicaCount { 344 return fmt.Errorf("bundle error: %s has %d replicas while %s has %d", 345 instancesStates[j].Object.Basename, 346 instancesStates[j].CurrentReplicaCount, 347 instancesStates[j-1].Object.Basename, 348 instancesStates[j-1].CurrentReplicaCount) 349 } 350 expectedReplicaCount = instancesStates[j].CurrentReplicaCount 351 } 352 return nil 353 } 354 355 func getIdentifier(ctx Context, object *api.Object) (state.InstancesIdentifier, error) { 356 obj, err := ctx.GetTemplateProvider().RawToObject(object.ObjectTemplatePath) 357 if err != nil { 358 return state.InstancesIdentifier{}, fmt.Errorf("reading template (%v) for identifier error: %v", object.ObjectTemplatePath, err) 359 } 360 gvk := obj.GroupVersionKind() 361 return state.InstancesIdentifier{ 362 Basename: object.Basename, 363 ObjectKind: gvk.Kind, 364 APIGroup: gvk.Group, 365 }, nil 366 } 367 368 func createNamespacesList(ctx Context, namespaceRange *api.NamespaceRange) []string { 369 if namespaceRange == nil { 370 // Returns "" which represents cluster level. 371 return []string{""} 372 } 373 374 nsList := make([]string, 0) 375 nsBasename := ctx.GetClusterFramework().GetAutomanagedNamespacePrefix() 376 if namespaceRange.Basename != nil { 377 nsBasename = *namespaceRange.Basename 378 } 379 380 for i := namespaceRange.Min; i <= namespaceRange.Max; i++ { 381 nsList = append(nsList, fmt.Sprintf("%v-%d", nsBasename, i)) 382 } 383 return nsList 384 } 385 386 func isErrsCritical(*errors.ErrorList) bool { 387 // TODO: define critical errors 388 return false 389 } 390 391 func cleanupResources(ctx Context, conf *api.Config) { 392 cleanupStartTime := time.Now() 393 ctx.GetManager().Dispose() 394 if *conf.Namespace.DeleteAutomanagedNamespaces { 395 if errList := ctx.GetClusterFramework().DeleteAutomanagedNamespaces(conf.Namespace.DeleteNamespaceTimeout.ToTimeDuration()); !errList.IsEmpty() { 396 klog.Errorf("Resource cleanup error: %v", errList.String()) 397 return 398 } 399 } 400 klog.V(2).Infof("Resources cleanup time: %v", time.Since(cleanupStartTime)) 401 } 402 403 func getReplicaCountOfNewObject(ctx Context, namespace string, object *api.Object) (int32, error) { 404 if object.ListUnknownObjectOptions == nil { 405 return 0, nil 406 } 407 klog.V(4).Infof("%s: new object detected, will list objects in order to find num replicas", object.Basename) 408 selector, err := metav1.LabelSelectorAsSelector(object.ListUnknownObjectOptions.LabelSelector) 409 if err != nil { 410 return 0, err 411 } 412 obj, err := ctx.GetTemplateProvider().RawToObject(object.ObjectTemplatePath) 413 if err != nil { 414 return 0, err 415 } 416 gvk := obj.GroupVersionKind() 417 gvr, _ := meta.UnsafeGuessKindToResource(gvk) 418 replicaCount, err := runtimeobjects.GetNumObjectsMatchingSelector( 419 ctx.GetClusterFramework().GetDynamicClients().GetClient(), 420 namespace, 421 gvr, 422 selector) 423 if err != nil { 424 return 0, nil 425 } 426 klog.V(4).Infof("%s: found %d replicas", object.Basename, replicaCount) 427 return int32(replicaCount), nil 428 }