diff --git a/pkg/apis/build/v1alpha1/build_types.go b/pkg/apis/build/v1alpha1/build_types.go index 5d0a1a2e..de0fc5b0 100644 --- a/pkg/apis/build/v1alpha1/build_types.go +++ b/pkg/apis/build/v1alpha1/build_types.go @@ -76,6 +76,10 @@ type BuildSpec struct { // Specified build timeout should be less than 24h. // Refer Go's ParseDuration documentation for expected format: https://golang.org/pkg/time/#ParseDuration Timeout string `json:"timeout,omitempty"` + + // If specified, the pod's scheduling constraints + // +optional + Affinity *corev1.Affinity `json:"affinity,omitempty"` } // TemplateKind defines the type of BuildTemplate used by the build. diff --git a/pkg/apis/build/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/build/v1alpha1/zz_generated.deepcopy.go index af4d93f8..bcfd5c3d 100644 --- a/pkg/apis/build/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/build/v1alpha1/zz_generated.deepcopy.go @@ -160,6 +160,15 @@ func (in *BuildSpec) DeepCopyInto(out *BuildSpec) { (*out)[key] = val } } + if in.Affinity != nil { + in, out := &in.Affinity, &out.Affinity + if *in == nil { + *out = nil + } else { + *out = new(v1.Affinity) + (*in).DeepCopyInto(*out) + } + } return } diff --git a/pkg/builder/cluster/convert/convert.go b/pkg/builder/cluster/convert/convert.go index 3be9151c..49a8991a 100644 --- a/pkg/builder/cluster/convert/convert.go +++ b/pkg/builder/cluster/convert/convert.go @@ -372,6 +372,7 @@ func FromCRD(build *v1alpha1.Build, kubeclient kubernetes.Interface) (*corev1.Po ServiceAccountName: build.Spec.ServiceAccountName, Volumes: volumes, NodeSelector: build.Spec.NodeSelector, + Affinity: build.Spec.Affinity, }, }, nil } @@ -501,6 +502,7 @@ func ToCRD(pod *corev1.Pod) (*v1alpha1.Build, error) { ServiceAccountName: podSpec.ServiceAccountName, Volumes: volumes, NodeSelector: podSpec.NodeSelector, + Affinity: podSpec.Affinity, }, }, nil } diff --git a/test/e2e/e2e.go b/test/e2e/e2e.go index 8a79c029..3ba8f08e 100644 --- a/test/e2e/e2e.go +++ b/test/e2e/e2e.go @@ -88,7 +88,6 @@ type buildClient struct { func (c *buildClient) watchBuild(name string) (*v1alpha1.Build, error) { ls := metav1.SingleObject(metav1.ObjectMeta{Name: name}) // TODO: Update watchBuild function to take this as parameter depending on test requirements - // Set build timeout to 120 seconds. This will trigger watch timeout error var timeout int64 = 120 ls.TimeoutSeconds = &timeout diff --git a/test/e2e/simple_test.go b/test/e2e/simple_test.go index 8e7176cd..da0d1bd0 100644 --- a/test/e2e/simple_test.go +++ b/test/e2e/simple_test.go @@ -206,6 +206,51 @@ func TestPendingBuild(t *testing.T) { } if _, err := clients.buildClient.watchBuild(buildName); err == nil { - t.Fatalf("watchBuild did not return watch timeout error") + t.Fatalf("watchBuild did not return expected `watch timeout` error") + } +} + +// TestPodAffinity tests that a build with non existent pod affinity does not scheduled +// and fails after watch timeout +func TestPodAffinity(t *testing.T) { + clients := setup(t) + + buildName := "affinity-build" + if _, err := clients.buildClient.builds.Create(&v1alpha1.Build{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: buildTestNamespace, + Name: buildName, + }, + Spec: v1alpha1.BuildSpec{ + Affinity: &corev1.Affinity{ + NodeAffinity: &corev1.NodeAffinity{ + // This node affinity rule says the pod can only be placed on a node with a label whose key is kubernetes.io/e2e-az-name + // and whose value is either e2e-az1 or e2e-az2. Test cluster does not have any nodes that meets this constraint so the build + // will wait for pod to scheduled until timeout. + RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ + NodeSelectorTerms: []corev1.NodeSelectorTerm{ + corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{ + corev1.NodeSelectorRequirement{ + Key: "kubernetes.io/e2e-az-name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"e2e-az1", "e2e-az2"}, + }}, + }, + }, + }, + }, + }, + Steps: []corev1.Container{{ + Image: "busybox", + Args: []string{"true"}, + }}, + }, + }); err != nil { + t.Fatalf("Error creating build: %v", err) + } + + if _, err := clients.buildClient.watchBuild(buildName); err == nil { + t.Fatalf("watchBuild did not return expected `watch timeout` error") } }