diff --git a/test/integration/framework/perf_utils.go b/test/integration/framework/perf_utils.go index 566308355fc..af3356bf678 100644 --- a/test/integration/framework/perf_utils.go +++ b/test/integration/framework/perf_utils.go @@ -21,7 +21,6 @@ import ( "fmt" v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/rand" clientset "k8s.io/client-go/kubernetes" @@ -33,29 +32,44 @@ const ( retries = 5 ) +// NodeTemplate is responsible for creating a v1.Node instance that is ready +// to be sent to the API server. +type NodeTemplate interface { + // GetNodeTemplate returns a node template for one out of many different nodes. + // Nodes with numbers in the range [index, index+count-1] will be created + // based on what GetNodeTemplate returns. It gets called multiple times + // with a fixed index and increasing count parameters. This number can, + // but doesn't have to be, used to modify parts of the node spec like + // for example a named reference to some other object. + GetNodeTemplate(index, count int) (*v1.Node, error) +} + +// StaticNodeTemplate returns an implementation of NodeTemplate for a fixed node that is the same regardless of the index. +func StaticNodeTemplate(node *v1.Node) NodeTemplate { + return (*staticNodeTemplate)(node) +} + +type staticNodeTemplate v1.Node + +// GetNodeTemplate implements [NodeTemplate.GetNodeTemplate] by returning the same node +// for each call. +func (s *staticNodeTemplate) GetNodeTemplate(index, count int) (*v1.Node, error) { + return (*v1.Node)(s), nil +} + // IntegrationTestNodePreparer holds configuration information for the test node preparer. type IntegrationTestNodePreparer struct { client clientset.Interface countToStrategy []testutils.CountToStrategy - nodeNamePrefix string - nodeSpec *v1.Node + nodeTemplate NodeTemplate } -// NewIntegrationTestNodePreparer creates an IntegrationTestNodePreparer configured with defaults. -func NewIntegrationTestNodePreparer(client clientset.Interface, countToStrategy []testutils.CountToStrategy, nodeNamePrefix string) testutils.TestNodePreparer { +// NewIntegrationTestNodePreparer creates an IntegrationTestNodePreparer with a given nodeTemplate. +func NewIntegrationTestNodePreparer(client clientset.Interface, countToStrategy []testutils.CountToStrategy, nodeTemplate NodeTemplate) testutils.TestNodePreparer { return &IntegrationTestNodePreparer{ client: client, countToStrategy: countToStrategy, - nodeNamePrefix: nodeNamePrefix, - } -} - -// NewIntegrationTestNodePreparerWithNodeSpec creates an IntegrationTestNodePreparer configured with nodespec. -func NewIntegrationTestNodePreparerWithNodeSpec(client clientset.Interface, countToStrategy []testutils.CountToStrategy, nodeSpec *v1.Node) testutils.TestNodePreparer { - return &IntegrationTestNodePreparer{ - client: client, - countToStrategy: countToStrategy, - nodeSpec: nodeSpec, + nodeTemplate: nodeTemplate, } } @@ -67,29 +81,12 @@ func (p *IntegrationTestNodePreparer) PrepareNodes(ctx context.Context, nextNode } klog.Infof("Making %d nodes", numNodes) - baseNode := &v1.Node{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: p.nodeNamePrefix, - }, - Status: v1.NodeStatus{ - Capacity: v1.ResourceList{ - v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI), - v1.ResourceCPU: resource.MustParse("4"), - v1.ResourceMemory: resource.MustParse("32Gi"), - }, - Phase: v1.NodeRunning, - Conditions: []v1.NodeCondition{ - {Type: v1.NodeReady, Status: v1.ConditionTrue}, - }, - }, - } - - if p.nodeSpec != nil { - baseNode = p.nodeSpec - } for i := 0; i < numNodes; i++ { - var err error + baseNode, err := p.nodeTemplate.GetNodeTemplate(i, numNodes) + if err != nil { + return fmt.Errorf("failed to get node template: %w", err) + } for retry := 0; retry < retries; retry++ { // Create nodes with the usual kubernetes.io/hostname label. // For that we need to know the name in advance, if we want to diff --git a/test/integration/scheduler_perf/scheduler_perf.go b/test/integration/scheduler_perf/scheduler_perf.go index 017620ea02d..b02e365d50e 100644 --- a/test/integration/scheduler_perf/scheduler_perf.go +++ b/test/integration/scheduler_perf/scheduler_perf.go @@ -1688,21 +1688,15 @@ func getNodePreparer(prefix string, cno *createNodesOp, clientset clientset.Inte nodeStrategy = cno.UniqueNodeLabelStrategy } + nodeTemplate := framework.StaticNodeTemplate(makeBaseNode(prefix)) if cno.NodeTemplatePath != nil { - node, err := getNodeSpecFromFile(cno.NodeTemplatePath) - if err != nil { - return nil, err - } - return framework.NewIntegrationTestNodePreparerWithNodeSpec( - clientset, - []testutils.CountToStrategy{{Count: cno.Count, Strategy: nodeStrategy}}, - node, - ), nil + nodeTemplate = nodeTemplateFromFile(*cno.NodeTemplatePath) } + return framework.NewIntegrationTestNodePreparer( clientset, []testutils.CountToStrategy{{Count: cno.Count, Strategy: nodeStrategy}}, - prefix, + nodeTemplate, ), nil } @@ -2089,9 +2083,11 @@ func getPodStrategy(cpo *createPodsOp) (testutils.TestPodCreateStrategy, error) return testutils.NewCreatePodWithPersistentVolumeStrategy(pvcTemplate, getCustomVolumeFactory(pvTemplate), podTemplate), nil } -func getNodeSpecFromFile(path *string) (*v1.Node, error) { +type nodeTemplateFromFile string + +func (f nodeTemplateFromFile) GetNodeTemplate(index, count int) (*v1.Node, error) { nodeSpec := &v1.Node{} - if err := getSpecFromFile(path, nodeSpec); err != nil { + if err := getSpecFromTextTemplateFile(string(f), map[string]any{"Index": index, "Count": count}, nodeSpec); err != nil { return nil, fmt.Errorf("parsing Node: %w", err) } return nodeSpec, nil diff --git a/test/integration/scheduler_perf/util.go b/test/integration/scheduler_perf/util.go index 879372af6f1..a4e45f069a9 100644 --- a/test/integration/scheduler_perf/util.go +++ b/test/integration/scheduler_perf/util.go @@ -30,6 +30,7 @@ import ( "time" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" @@ -227,6 +228,27 @@ func makeBasePod() *v1.Pod { return basePod } +// makeBaseNode creates a Node object with given nodeNamePrefix to be used as a template. +func makeBaseNode(nodeNamePrefix string) *v1.Node { + return &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: nodeNamePrefix, + }, + Status: v1.NodeStatus{ + Capacity: v1.ResourceList{ + v1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI), + v1.ResourceCPU: resource.MustParse("4"), + v1.ResourceMemory: resource.MustParse("32Gi"), + }, + Phase: v1.NodeRunning, + Conditions: []v1.NodeCondition{ + {Type: v1.NodeReady, Status: v1.ConditionTrue}, + }, + }, + } + +} + func dataItems2JSONFile(dataItems DataItems, namePrefix string) error { // perfdash expects all data items to have the same set of labels. It // then renders drop-down buttons for each label with all values found