diff --git a/go.mod b/go.mod index f4694df6748f..c35797797886 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/Pallinder/go-randomdata v1.2.0 github.com/aws/aws-sdk-go v1.38.11 github.com/go-logr/zapr v0.2.0 + github.com/imdario/mergo v0.3.10 github.com/mitchellh/hashstructure/v2 v2.0.1 github.com/onsi/ginkgo v1.14.2 github.com/onsi/gomega v1.10.3 diff --git a/pkg/cloudprovider/aws/suite_test.go b/pkg/cloudprovider/aws/suite_test.go index abf7ddf9ab25..56855133961b 100644 --- a/pkg/cloudprovider/aws/suite_test.go +++ b/pkg/cloudprovider/aws/suite_test.go @@ -144,9 +144,7 @@ var _ = Describe("Allocation", func() { {SubnetId: aws.String("test-subnet-3"), AvailabilityZone: aws.String("test-zone-1c")}, }} // Setup - pod := test.PodWith(test.PodOptions{ - Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, - }) + pod := test.PendingPod() ExpectCreatedWithStatus(env.Client, pod) ExpectCreated(env.Client, provisioner) ExpectEventuallyReconciled(env.Client, provisioner) @@ -180,9 +178,7 @@ var _ = Describe("Allocation", func() { }} // Setup provisioner.Spec.Zones = []string{"test-zone-1a", "test-zone-1b"} - pod := test.PodWith(test.PodOptions{ - Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, - }) + pod := test.PendingPod() ExpectCreatedWithStatus(env.Client, pod) ExpectCreated(env.Client, provisioner) ExpectEventuallyReconciled(env.Client, provisioner) @@ -213,10 +209,7 @@ var _ = Describe("Allocation", func() { }} // Setup provisioner.Spec.Zones = []string{"test-zone-1a", "test-zone-1b"} - pod := test.PodWith(test.PodOptions{ - NodeSelector: map[string]string{v1alpha1.ZoneLabelKey: "test-zone-1c"}, - Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, - }) + pod := test.PendingPodWith(test.PodOptions{NodeSelector: map[string]string{v1alpha1.ZoneLabelKey: "test-zone-1c"}}) ExpectCreatedWithStatus(env.Client, pod) ExpectCreated(env.Client, provisioner) ExpectEventuallyReconciled(env.Client, provisioner) @@ -237,14 +230,8 @@ var _ = Describe("Allocation", func() { }) It("should launch separate instances for pods with different node selectors", func() { // Setup - pod1Options := test.PodOptions{ - NodeSelector: map[string]string{"node.k8s.aws/launch-template-id": "abc123"}, - Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}} - pod2Options := test.PodOptions{ - NodeSelector: map[string]string{"node.k8s.aws/launch-template-id": "34sy4s"}, - Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}} - pod1 := test.PodWith(pod1Options) - pod2 := test.PodWith(pod2Options) + pod1 := test.PendingPodWith(test.PodOptions{NodeSelector: map[string]string{"node.k8s.aws/launch-template-id": "abc123"}}) + pod2 := test.PendingPodWith(test.PodOptions{NodeSelector: map[string]string{"node.k8s.aws/launch-template-id": "34sy4s"}}) ExpectCreatedWithStatus(env.Client, pod1, pod2) ExpectCreated(env.Client, provisioner) ExpectEventuallyReconciled(env.Client, provisioner) diff --git a/pkg/controllers/provisioning/v1alpha1/allocation/suite_test.go b/pkg/controllers/provisioning/v1alpha1/allocation/suite_test.go index a4496d03f755..e96ea41c142f 100644 --- a/pkg/controllers/provisioning/v1alpha1/allocation/suite_test.go +++ b/pkg/controllers/provisioning/v1alpha1/allocation/suite_test.go @@ -87,14 +87,7 @@ var _ = Describe("Allocation", func() { Context("Reconcilation", func() { It("should provision nodes for unconstrained pods", func() { - pods := []*v1.Pod{ - test.PodWith(test.PodOptions{ - Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, - }), - test.PodWith(test.PodOptions{ - Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, - }), - } + pods := []*v1.Pod{test.PendingPod(), test.PendingPod()} for _, pod := range pods { ExpectCreatedWithStatus(env.Client, pod) } @@ -113,45 +106,42 @@ var _ = Describe("Allocation", func() { It("should provision nodes for pods with supported node selectors", func() { coschedulable := []client.Object{ // Unconstrained - test.PodWith(test.PodOptions{ - Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, - }), + test.PendingPod(), // Constrained by provisioner - test.PodWith(test.PodOptions{ + test.PendingPodWith(test.PodOptions{ NodeSelector: map[string]string{v1alpha1.ProvisionerNameLabelKey: provisioner.Name, v1alpha1.ProvisionerNamespaceLabelKey: provisioner.Namespace}, - Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, }), } schedulable := []client.Object{ // Constrained by zone - test.PodWith(test.PodOptions{ + test.PendingPodWith(test.PodOptions{ NodeSelector: map[string]string{v1alpha1.ZoneLabelKey: "test-zone-1"}, Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, }), // Constrained by instanceType - test.PodWith(test.PodOptions{ + test.PendingPodWith(test.PodOptions{ NodeSelector: map[string]string{v1alpha1.InstanceTypeLabelKey: "test-instance-type-1"}, Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, }), // Constrained by architecture - test.PodWith(test.PodOptions{ + test.PendingPodWith(test.PodOptions{ NodeSelector: map[string]string{v1alpha1.ArchitectureLabelKey: "test-architecture-1"}, Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, }), // Constrained by operating system - test.PodWith(test.PodOptions{ + test.PendingPodWith(test.PodOptions{ NodeSelector: map[string]string{v1alpha1.OperatingSystemLabelKey: "test-operating-system-1"}, Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, }), // Constrained by arbitrary label - test.PodWith(test.PodOptions{ + test.PendingPodWith(test.PodOptions{ NodeSelector: map[string]string{"foo": "bar"}, Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, }), } unschedulable := []client.Object{ // Ignored, matches another provisioner - test.PodWith(test.PodOptions{ + test.PendingPodWith(test.PodOptions{ NodeSelector: map[string]string{v1alpha1.ProvisionerNameLabelKey: "test", v1alpha1.ProvisionerNamespaceLabelKey: "test"}, Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, }), @@ -184,33 +174,33 @@ var _ = Describe("Allocation", func() { provisioner.Spec.Taints = []v1.Taint{{Key: "test-key", Value: "test-value", Effect: v1.TaintEffectNoSchedule}} schedulable := []client.Object{ // Tolerates with OpExists - test.PodWith(test.PodOptions{ + test.PendingPodWith(test.PodOptions{ Tolerations: []v1.Toleration{{Key: "test-key", Operator: v1.TolerationOpExists}}, Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, }), // Tolerates with OpEqual - test.PodWith(test.PodOptions{ + test.PendingPodWith(test.PodOptions{ Tolerations: []v1.Toleration{{Key: "test-key", Value: "test-value", Operator: v1.TolerationOpEqual}}, Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, }), } unschedulable := []client.Object{ // Missing toleration - test.PodWith(test.PodOptions{ + test.PendingPodWith(test.PodOptions{ Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, }), // key mismatch with OpExists - test.PodWith(test.PodOptions{ + test.PendingPodWith(test.PodOptions{ Tolerations: []v1.Toleration{{Key: "invalid", Operator: v1.TolerationOpExists}}, Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, }), // value mismatch with OpEqual - test.PodWith(test.PodOptions{ + test.PendingPodWith(test.PodOptions{ Tolerations: []v1.Toleration{{Key: "test-key", Value: "invalid", Operator: v1.TolerationOpEqual}}, Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, }), // key mismatch with OpEqual - test.PodWith(test.PodOptions{ + test.PendingPodWith(test.PodOptions{ Tolerations: []v1.Toleration{{Key: "invalid", Value: "test-value", Operator: v1.TolerationOpEqual}}, Conditions: []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}}, }), diff --git a/pkg/controllers/provisioning/v1alpha1/reallocation/suite_test.go b/pkg/controllers/provisioning/v1alpha1/reallocation/suite_test.go index 563dded3ffea..38b7d54bbb74 100644 --- a/pkg/controllers/provisioning/v1alpha1/reallocation/suite_test.go +++ b/pkg/controllers/provisioning/v1alpha1/reallocation/suite_test.go @@ -118,7 +118,7 @@ var _ = Describe("Reallocation", func() { v1alpha1.ProvisionerTTLKey: time.Now().Add(time.Duration(100) * time.Second).Format(time.RFC3339), }, }) - pod := test.PodWith(test.PodOptions{ + pod := test.PendingPodWith(test.PodOptions{ Name: strings.ToLower(randomdata.SillyName()), Namespace: provisioner.Namespace, NodeName: node.Name, diff --git a/pkg/test/objects.go b/pkg/test/nodes.go similarity index 61% rename from pkg/test/objects.go rename to pkg/test/nodes.go index 56a3a6b1678e..ef581c4241b1 100644 --- a/pkg/test/objects.go +++ b/pkg/test/nodes.go @@ -22,48 +22,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -type PodOptions struct { - Name string - Namespace string - Image string - NodeName string - ResourceRequests v1.ResourceList - NodeSelector map[string]string - Tolerations []v1.Toleration - Conditions []v1.PodCondition -} - -func PodWith(options PodOptions) *v1.Pod { - if options.Name == "" { - options.Name = strings.ToLower(randomdata.SillyName()) - } - if options.Namespace == "" { - options.Namespace = "default" - } - if options.Image == "" { - options.Image = "k8s.gcr.io/pause" - } - return &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: options.Name, - Namespace: options.Namespace, - }, - Spec: v1.PodSpec{ - NodeSelector: options.NodeSelector, - Tolerations: options.Tolerations, - Containers: []v1.Container{{ - Name: options.Name, - Image: options.Image, - Resources: v1.ResourceRequirements{ - Requests: options.ResourceRequests, - }, - }}, - NodeName: options.NodeName, - }, - Status: v1.PodStatus{Conditions: options.Conditions}, - } -} - type NodeOptions struct { Name string Labels map[string]string diff --git a/pkg/test/pods.go b/pkg/test/pods.go new file mode 100644 index 000000000000..2166b2fab9ae --- /dev/null +++ b/pkg/test/pods.go @@ -0,0 +1,78 @@ +package test + +import ( + "fmt" + "strings" + + "github.com/Pallinder/go-randomdata" + "github.com/imdario/mergo" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// PodOptions customizes a Pod. +type PodOptions struct { + Name string + Namespace string + Image string + NodeName string + ResourceRequests v1.ResourceList + NodeSelector map[string]string + Tolerations []v1.Toleration + Conditions []v1.PodCondition +} + +func defaults(options PodOptions) *v1.Pod { + if options.Name == "" { + options.Name = strings.ToLower(randomdata.SillyName()) + } + if options.Namespace == "" { + options.Namespace = "default" + } + if options.Image == "" { + options.Image = "k8s.gcr.io/pause" + } + if len(options.Conditions) == 0 { + options.Conditions = []v1.PodCondition{{Type: v1.PodScheduled, Reason: v1.PodReasonUnschedulable, Status: v1.ConditionFalse}} + } + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: options.Name, + Namespace: options.Namespace, + }, + Spec: v1.PodSpec{ + NodeSelector: options.NodeSelector, + Tolerations: options.Tolerations, + Containers: []v1.Container{{ + Name: options.Name, + Image: options.Image, + Resources: v1.ResourceRequirements{ + Requests: options.ResourceRequests, + }, + }}, + NodeName: options.NodeName, + }, + Status: v1.PodStatus{Conditions: options.Conditions}, + } +} + +// PendingPod creates a pending test pod with the minimal set of other +// fields defaulted to something sane. +func PendingPod() *v1.Pod { + return defaults(PodOptions{}) +} + +// PendingPodWith creates a pending test pod with fields overridden by +// options. +func PendingPodWith(options PodOptions) *v1.Pod { + return PodWith(PendingPod(), options) +} + +// PodWith overrides, in-place, pod with any non-zero elements of +// options. It returns the same pod simply for ease of use. +func PodWith(pod *v1.Pod, options PodOptions) *v1.Pod { + if err := mergo.Merge(pod, defaults(options), mergo.WithOverride); err != nil { + panic(fmt.Sprintf("unexpected error in test code: %v", err)) + } + return pod +}