From 5c495d5927881af37a30efded76254ab84577ea9 Mon Sep 17 00:00:00 2001 From: Francesco Canovai Date: Fri, 12 May 2023 11:35:30 +0200 Subject: [PATCH] Wait for custom conditions on objects (#308) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow waiting for conditions on custom resources Add a waitOption key to the objects, to customize how to wait for objects to be ready. Implement a forCondition option that allow to wait for a condition to be true. * Fix race condition when waiting for the iteration Fix an issue where, if podwait was set to true, we could exit from our waiters because listing the objects could initially return an empty list. * Update create.go * Update create.go * Update pkg/burner/create.go --------- Co-authored-by: Raúl Sevilla --- docs/configuration.md | 12 +++++++- pkg/burner/create.go | 2 ++ pkg/burner/job.go | 1 + pkg/burner/waiters.go | 64 +++++++++++++++++++++++++------------------ pkg/config/types.go | 7 +++++ 5 files changed, 58 insertions(+), 28 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index ad272f7e0..78a513f2f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -77,8 +77,18 @@ Each object element supports the following parameters: | inputVars | Map of arbitrary input variables to inject to the object template | Object | - | - | | namespaced | Whether to create a namespaced object or not | Boolean | false | true | | wait | Wait for object to be ready[^1] | Boolean | false | true | +| waitOptions | Customize how to wait for object to be ready | Object | - | - | -[^1]: Kube-burner is only able to wait for a subset of resources. These resources are available in the `pkg/burner/waiters.go` file +[^1]: Kube-burner is only able to wait for a subset of resources, unless `waitOptions` are specified. +These resources are available in the `pkg/burner/waiters.go` file + +### Wait Options + +If you want to override the default waiter behaviors, you can specify wait options for your objects. + +| Option | Description | Type | Example | Default | +|--------------|---------------------------------------------------------|---------|-------------|---------| +| forCondition | Wait for the object condition with this name to be true | String | myCondition | - | ### Default labels diff --git a/pkg/burner/create.go b/pkg/burner/create.go index 80c6cf924..1597cc389 100644 --- a/pkg/burner/create.go +++ b/pkg/burner/create.go @@ -83,6 +83,7 @@ func setupCreateJob(jobConfig config.Job) Executor { inputVars: o.InputVars, namespaced: o.Namespaced, wait: o.Wait, + waitOptions: o.WaitOptions, } // If any of the objects is namespaced, we configure the job to create namepaces if o.Namespaced { @@ -141,6 +142,7 @@ func (ex *Executor) RunCreateJob(iterationStart, iterationEnd int) { if ex.Config.PodWait { if !ex.Config.NamespacedIterations || !namespacesWaited[ns] { log.Infof("Waiting up to %s for actions to be completed in namespace %s", ex.Config.MaxWaitTimeout, ns) + wg.Wait() ex.waitForObjects(ns) namespacesWaited[ns] = true } diff --git a/pkg/burner/job.go b/pkg/burner/job.go index 50c1256e9..c71a9dde6 100644 --- a/pkg/burner/job.go +++ b/pkg/burner/job.go @@ -48,6 +48,7 @@ type object struct { namespaced bool kind string wait bool + waitOptions config.WaitOptions } // Executor contains the information required to execute a job diff --git a/pkg/burner/waiters.go b/pkg/burner/waiters.go index e2bc231ab..6ff84baea 100644 --- a/pkg/burner/waiters.go +++ b/pkg/burner/waiters.go @@ -34,33 +34,37 @@ func (ex *Executor) waitForObjects(ns string) { for _, obj := range ex.objects { if obj.wait { wg.Add(1) - switch obj.kind { - case "Deployment": - go waitForDeployments(ns, ex.Config.MaxWaitTimeout, &wg) - case "ReplicaSet": - go waitForRS(ns, ex.Config.MaxWaitTimeout, &wg) - case "ReplicationController": - go waitForRC(ns, ex.Config.MaxWaitTimeout, &wg) - case "StatefulSet": - go waitForStatefulSet(ns, ex.Config.MaxWaitTimeout, &wg) - case "DaemonSet": - go waitForDS(ns, ex.Config.MaxWaitTimeout, &wg) - case "Pod": - go waitForPod(ns, ex.Config.MaxWaitTimeout, &wg) - case "Build", "BuildConfig": - go waitForBuild(ns, ex.Config.MaxWaitTimeout, obj.replicas, &wg) - case "VirtualMachine": - go waitForVM(ns, ex.Config.MaxWaitTimeout, &wg) - case "VirtualMachineInstance": - go waitForVMI(ns, ex.Config.MaxWaitTimeout, &wg) - case "VirtualMachineInstanceReplicaSet": - go waitForVMIRS(ns, ex.Config.MaxWaitTimeout, &wg) - case "Job": - go waitForJob(ns, ex.Config.MaxWaitTimeout, &wg) - case "PersistentVolumeClaim": - go waitForPVC(ns, ex.Config.MaxWaitTimeout, &wg) - default: - wg.Done() + if obj.waitOptions.ForCondition != "" { + go waitForCondition(obj.gvr, ns, obj.waitOptions.ForCondition, ex.Config.MaxWaitTimeout, &wg) + } else { + switch obj.kind { + case "Deployment": + go waitForDeployments(ns, ex.Config.MaxWaitTimeout, &wg) + case "ReplicaSet": + go waitForRS(ns, ex.Config.MaxWaitTimeout, &wg) + case "ReplicationController": + go waitForRC(ns, ex.Config.MaxWaitTimeout, &wg) + case "StatefulSet": + go waitForStatefulSet(ns, ex.Config.MaxWaitTimeout, &wg) + case "DaemonSet": + go waitForDS(ns, ex.Config.MaxWaitTimeout, &wg) + case "Pod": + go waitForPod(ns, ex.Config.MaxWaitTimeout, &wg) + case "Build", "BuildConfig": + go waitForBuild(ns, ex.Config.MaxWaitTimeout, obj.replicas, &wg) + case "VirtualMachine": + go waitForVM(ns, ex.Config.MaxWaitTimeout, &wg) + case "VirtualMachineInstance": + go waitForVMI(ns, ex.Config.MaxWaitTimeout, &wg) + case "VirtualMachineInstanceReplicaSet": + go waitForVMIRS(ns, ex.Config.MaxWaitTimeout, &wg) + case "Job": + go waitForJob(ns, ex.Config.MaxWaitTimeout, &wg) + case "PersistentVolumeClaim": + go waitForPVC(ns, ex.Config.MaxWaitTimeout, &wg) + default: + wg.Done() + } } } } @@ -221,6 +225,12 @@ func waitForJob(ns string, maxWaitTimeout time.Duration, wg *sync.WaitGroup) { verifyCondition(gvr, ns, "Complete", maxWaitTimeout) } +func waitForCondition(gvr schema.GroupVersionResource, ns, condition string, maxWaitTimeout time.Duration, + wg *sync.WaitGroup) { + defer wg.Done() + verifyCondition(gvr, ns, condition, maxWaitTimeout) +} + func verifyCondition(gvr schema.GroupVersionResource, ns, condition string, maxWaitTimeout time.Duration) { var uObj types.UnstructuredContent wait.PollUntilContextTimeout(context.TODO(), 10*time.Second, maxWaitTimeout, true, func(ctx context.Context) (done bool, err error) { diff --git a/pkg/config/types.go b/pkg/config/types.go index 2361dd423..412827551 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -85,6 +85,8 @@ type Object struct { Namespaced bool `yaml:"namespaced" json:"namespaced"` // Wait for resource to be ready, it doesn't apply to all resources Wait bool `yaml:"wait" json:"wait"` + // WaitOptions define custom behaviors when waiting for objects creation + WaitOptions WaitOptions `yaml:"waitOptions" json:"waitOptions,omitempty"` } // Job defines a kube-burner job @@ -140,3 +142,8 @@ type Job struct { // Churn delay between sets ChurnDelay time.Duration `yaml:"churnDelay" json:"churnDelay,omitempty"` } + +type WaitOptions struct { + // ForCondition wait for this condition to become true + ForCondition string `yaml:"forCondition" json:"forCondition,omitempty"` +}