Skip to content

Commit 51cafb0

Browse files
committed
scheduler_perf: more useful errors for configuration mistakes
Before, the first error was reported, which typically was the "invalid op code" error from the createAny operation: scheduler_perf.go:900: parsing test cases error: error unmarshaling JSON: while decoding JSON: cannot unmarshal {"collectMetrics":true,"count":10,"duration":"30s","namespace":"test","opcode":"createPods","podTemplatePath":"config/dra/pod-with-claim-template.yaml","steadyState":true} into any known op type: invalid opcode "createPods"; expected "createAny" Now the opcode is determined first, then decoding into exactly the matching operation is tried and validated. Unknown fields are an error. In the case above, decoding a string into time.Duration failed: scheduler_test.go:29: parsing test cases error: error unmarshaling JSON: while decoding JSON: decoding {"collectMetrics":true,"count":10,"duration":"30s","namespace":"test","opcode":"createPods","podTemplatePath":"config/dra/pod-with-claim-template.yaml","steadyState":true} into *benchmark.createPodsOp: json: cannot unmarshal string into Go struct field createPodsOp.Duration of type time.Duration Some typos: scheduler_test.go:29: parsing test cases error: error unmarshaling JSON: while decoding JSON: unknown opcode "sleeep" in {"duration":"5s","opcode":"sleeep"} scheduler_test.go:29: parsing test cases error: error unmarshaling JSON: while decoding JSON: decoding {"countParram":"$deletingPods","deletePodsPerSecond":50,"opcode":"createPods"} into *benchmark.createPodsOp: json: unknown field "countParram"
1 parent 7bbb346 commit 51cafb0

File tree

3 files changed

+37
-63
lines changed

3 files changed

+37
-63
lines changed

test/integration/scheduler_perf/create.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,6 @@ type createAny struct {
5656
var _ runnableOp = &createAny{}
5757

5858
func (c *createAny) isValid(allowParameterization bool) error {
59-
if c.Opcode != createAnyOpcode {
60-
return fmt.Errorf("invalid opcode %q; expected %q", c.Opcode, createAnyOpcode)
61-
}
6259
if c.TemplatePath == "" {
6360
return fmt.Errorf("TemplatePath must be set")
6461
}

test/integration/scheduler_perf/dra.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,6 @@ var _ realOp = &createResourceClaimsOp{}
5050
var _ runnableOp = &createResourceClaimsOp{}
5151

5252
func (op *createResourceClaimsOp) isValid(allowParameterization bool) error {
53-
if op.Opcode != createResourceClaimsOpcode {
54-
return fmt.Errorf("invalid opcode %q; expected %q", op.Opcode, createResourceClaimsOpcode)
55-
}
5653
if !isValidCount(allowParameterization, op.Count, op.CountParam) {
5754
return fmt.Errorf("invalid Count=%d / CountParam=%q", op.Count, op.CountParam)
5855
}
@@ -139,9 +136,6 @@ var _ realOp = &createResourceDriverOp{}
139136
var _ runnableOp = &createResourceDriverOp{}
140137

141138
func (op *createResourceDriverOp) isValid(allowParameterization bool) error {
142-
if op.Opcode != createResourceDriverOpcode {
143-
return fmt.Errorf("invalid opcode %q; expected %q", op.Opcode, createResourceDriverOpcode)
144-
}
145139
if !isValidCount(allowParameterization, op.MaxClaimsPerNode, op.MaxClaimsPerNodeParam) {
146140
return fmt.Errorf("invalid MaxClaimsPerNode=%d / MaxClaimsPerNodeParam=%q", op.MaxClaimsPerNode, op.MaxClaimsPerNodeParam)
147141
}

test/integration/scheduler_perf/scheduler_perf.go

Lines changed: 37 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package benchmark
1818

1919
import (
20+
"bytes"
2021
"context"
2122
"encoding/json"
2223
"errors"
@@ -404,43 +405,52 @@ type op struct {
404405
// UnmarshalJSON is a custom unmarshaler for the op struct since we don't know
405406
// which op we're decoding at runtime.
406407
func (op *op) UnmarshalJSON(b []byte) error {
407-
possibleOps := []realOp{
408-
&createAny{},
409-
&createNodesOp{},
410-
&createNamespacesOp{},
411-
&createPodsOp{},
412-
&createPodSetsOp{},
413-
&deletePodsOp{},
414-
&createResourceClaimsOp{},
415-
&createResourceDriverOp{},
416-
&churnOp{},
417-
&barrierOp{},
418-
&sleepOp{},
419-
&startCollectingMetricsOp{},
420-
&stopCollectingMetricsOp{},
408+
possibleOps := map[operationCode]realOp{
409+
createAnyOpcode: &createAny{},
410+
createNodesOpcode: &createNodesOp{},
411+
createNamespacesOpcode: &createNamespacesOp{},
412+
createPodsOpcode: &createPodsOp{},
413+
createPodSetsOpcode: &createPodSetsOp{},
414+
deletePodsOpcode: &deletePodsOp{},
415+
createResourceClaimsOpcode: &createResourceClaimsOp{},
416+
createResourceDriverOpcode: &createResourceDriverOp{},
417+
churnOpcode: &churnOp{},
418+
barrierOpcode: &barrierOp{},
419+
sleepOpcode: &sleepOp{},
420+
startCollectingMetricsOpcode: &startCollectingMetricsOp{},
421+
stopCollectingMetricsOpcode: &stopCollectingMetricsOp{},
421422
// TODO(#94601): add a delete nodes op to simulate scaling behaviour?
422423
}
423-
var firstError error
424-
for _, possibleOp := range possibleOps {
425-
if err := json.Unmarshal(b, possibleOp); err == nil {
426-
if err2 := possibleOp.isValid(true); err2 == nil {
427-
op.realOp = possibleOp
428-
return nil
429-
} else if firstError == nil {
430-
// Don't return an error yet. Even though this op is invalid, it may
431-
// still match other possible ops.
432-
firstError = err2
433-
}
434-
}
424+
// First determine the opcode using lenient decoding (= ignore extra fields).
425+
var possibleOp struct {
426+
Opcode operationCode
427+
}
428+
if err := json.Unmarshal(b, &possibleOp); err != nil {
429+
return fmt.Errorf("decoding opcode from %s: %w", string(b), err)
430+
}
431+
realOp, ok := possibleOps[possibleOp.Opcode]
432+
if !ok {
433+
return fmt.Errorf("unknown opcode %q in %s", possibleOp.Opcode, string(b))
435434
}
436-
return fmt.Errorf("cannot unmarshal %s into any known op type: %w", string(b), firstError)
435+
decoder := json.NewDecoder(bytes.NewReader(b))
436+
decoder.DisallowUnknownFields()
437+
if err := decoder.Decode(realOp); err != nil {
438+
return fmt.Errorf("decoding %s into %T: %w", string(b), realOp, err)
439+
}
440+
if err := realOp.isValid(true); err != nil {
441+
return fmt.Errorf("%s not valid for %T: %w", string(b), realOp, err)
442+
}
443+
op.realOp = realOp
444+
return nil
437445
}
438446

439447
// realOp is an interface that is implemented by different structs. To evaluate
440448
// the validity of ops at parse-time, a isValid function must be implemented.
441449
type realOp interface {
442450
// isValid verifies the validity of the op args such as node/pod count. Note
443451
// that we don't catch undefined parameters at this stage.
452+
//
453+
// This returns errInvalidOp if the configured operation does not match.
444454
isValid(allowParameterization bool) error
445455
// collectsMetrics checks if the op collects metrics.
446456
collectsMetrics() bool
@@ -497,9 +507,6 @@ type createNodesOp struct {
497507
}
498508

499509
func (cno *createNodesOp) isValid(allowParameterization bool) error {
500-
if cno.Opcode != createNodesOpcode {
501-
return fmt.Errorf("invalid opcode %q", cno.Opcode)
502-
}
503510
if !isValidCount(allowParameterization, cno.Count, cno.CountParam) {
504511
return fmt.Errorf("invalid Count=%d / CountParam=%q", cno.Count, cno.CountParam)
505512
}
@@ -538,9 +545,6 @@ type createNamespacesOp struct {
538545
}
539546

540547
func (cmo *createNamespacesOp) isValid(allowParameterization bool) error {
541-
if cmo.Opcode != createNamespacesOpcode {
542-
return fmt.Errorf("invalid opcode %q", cmo.Opcode)
543-
}
544548
if !isValidCount(allowParameterization, cmo.Count, cmo.CountParam) {
545549
return fmt.Errorf("invalid Count=%d / CountParam=%q", cmo.Count, cmo.CountParam)
546550
}
@@ -595,9 +599,6 @@ type createPodsOp struct {
595599
}
596600

597601
func (cpo *createPodsOp) isValid(allowParameterization bool) error {
598-
if cpo.Opcode != createPodsOpcode {
599-
return fmt.Errorf("invalid opcode %q; expected %q", cpo.Opcode, createPodsOpcode)
600-
}
601602
if !isValidCount(allowParameterization, cpo.Count, cpo.CountParam) {
602603
return fmt.Errorf("invalid Count=%d / CountParam=%q", cpo.Count, cpo.CountParam)
603604
}
@@ -641,9 +642,6 @@ type createPodSetsOp struct {
641642
}
642643

643644
func (cpso *createPodSetsOp) isValid(allowParameterization bool) error {
644-
if cpso.Opcode != createPodSetsOpcode {
645-
return fmt.Errorf("invalid opcode %q; expected %q", cpso.Opcode, createPodSetsOpcode)
646-
}
647645
if !isValidCount(allowParameterization, cpso.Count, cpso.CountParam) {
648646
return fmt.Errorf("invalid Count=%d / CountParam=%q", cpso.Count, cpso.CountParam)
649647
}
@@ -729,9 +727,6 @@ type churnOp struct {
729727
}
730728

731729
func (co *churnOp) isValid(_ bool) error {
732-
if co.Opcode != churnOpcode {
733-
return fmt.Errorf("invalid opcode %q", co.Opcode)
734-
}
735730
if co.Mode != Recreate && co.Mode != Create {
736731
return fmt.Errorf("invalid mode: %v. must be one of %v", co.Mode, []string{Recreate, Create})
737732
}
@@ -767,9 +762,6 @@ type barrierOp struct {
767762
}
768763

769764
func (bo *barrierOp) isValid(allowParameterization bool) error {
770-
if bo.Opcode != barrierOpcode {
771-
return fmt.Errorf("invalid opcode %q", bo.Opcode)
772-
}
773765
return nil
774766
}
775767

@@ -805,9 +797,6 @@ func (so *sleepOp) UnmarshalJSON(data []byte) (err error) {
805797
}
806798

807799
func (so *sleepOp) isValid(_ bool) error {
808-
if so.Opcode != sleepOpcode {
809-
return fmt.Errorf("invalid opcode %q; expected %q", so.Opcode, sleepOpcode)
810-
}
811800
return nil
812801
}
813802

@@ -831,9 +820,6 @@ type startCollectingMetricsOp struct {
831820
}
832821

833822
func (scm *startCollectingMetricsOp) isValid(_ bool) error {
834-
if scm.Opcode != startCollectingMetricsOpcode {
835-
return fmt.Errorf("invalid opcode %q; expected %q", scm.Opcode, startCollectingMetricsOpcode)
836-
}
837823
if len(scm.Namespaces) == 0 {
838824
return fmt.Errorf("namespaces cannot be empty")
839825
}
@@ -857,9 +843,6 @@ type stopCollectingMetricsOp struct {
857843
}
858844

859845
func (scm *stopCollectingMetricsOp) isValid(_ bool) error {
860-
if scm.Opcode != stopCollectingMetricsOpcode {
861-
return fmt.Errorf("invalid opcode %q; expected %q", scm.Opcode, stopCollectingMetricsOpcode)
862-
}
863846
return nil
864847
}
865848

0 commit comments

Comments
 (0)