Browse code

Add build timeout, by setting ActiveDeadlineSeconds on a build pod

Maciej Szulik authored on 2015/09/28 16:06:40
Showing 23 changed files
... ...
@@ -13819,6 +13819,11 @@
13819 13819
      "resources": {
13820 13820
       "$ref": "v1.ResourceRequirements",
13821 13821
       "description": "the desired compute resources the build should have"
13822
+     },
13823
+     "completionDeadlineSeconds": {
13824
+      "type": "integer",
13825
+      "format": "int64",
13826
+      "description": "optional duration in seconds the build may be active on a node before the system will actively try to mark it failed and kill associated containers; value must be a positive integer"
13822 13827
      }
13823 13828
     }
13824 13829
    },
... ...
@@ -14481,6 +14486,11 @@
14481 14481
      "resources": {
14482 14482
       "$ref": "v1.ResourceRequirements",
14483 14483
       "description": "the desired compute resources the build should have"
14484
+     },
14485
+     "completionDeadlineSeconds": {
14486
+      "type": "integer",
14487
+      "format": "int64",
14488
+      "description": "optional duration in seconds the build may be active on a node before the system will actively try to mark it failed and kill associated containers; value must be a positive integer"
14484 14489
      }
14485 14490
     }
14486 14491
    },
... ...
@@ -918,6 +918,12 @@ func deepCopy_api_BuildSpec(in buildapi.BuildSpec, out *buildapi.BuildSpec, c *c
918 918
 	} else {
919 919
 		out.Resources = newVal.(pkgapi.ResourceRequirements)
920 920
 	}
921
+	if in.CompletionDeadlineSeconds != nil {
922
+		out.CompletionDeadlineSeconds = new(int64)
923
+		*out.CompletionDeadlineSeconds = *in.CompletionDeadlineSeconds
924
+	} else {
925
+		out.CompletionDeadlineSeconds = nil
926
+	}
921 927
 	return nil
922 928
 }
923 929
 
... ...
@@ -49,6 +49,7 @@ func checkDescriptions(objType reflect.Type, seen *map[reflect.Type]bool, t *tes
49 49
 
50 50
 		descriptionTag := structField.Tag.Get("description")
51 51
 		if len(descriptionTag) == 0 {
52
+			t.Errorf("%v", structField.Tag)
52 53
 			t.Errorf("%v.%v does not have a description", objType, structField.Name)
53 54
 		}
54 55
 
... ...
@@ -771,6 +771,12 @@ func convert_api_BuildSpec_To_v1_BuildSpec(in *buildapi.BuildSpec, out *apiv1.Bu
771 771
 	if err := convert_api_ResourceRequirements_To_v1_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil {
772 772
 		return err
773 773
 	}
774
+	if in.CompletionDeadlineSeconds != nil {
775
+		out.CompletionDeadlineSeconds = new(int64)
776
+		*out.CompletionDeadlineSeconds = *in.CompletionDeadlineSeconds
777
+	} else {
778
+		out.CompletionDeadlineSeconds = nil
779
+	}
774 780
 	return nil
775 781
 }
776 782
 
... ...
@@ -1159,6 +1165,12 @@ func convert_v1_BuildSpec_To_api_BuildSpec(in *apiv1.BuildSpec, out *buildapi.Bu
1159 1159
 	if err := convert_v1_ResourceRequirements_To_api_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil {
1160 1160
 		return err
1161 1161
 	}
1162
+	if in.CompletionDeadlineSeconds != nil {
1163
+		out.CompletionDeadlineSeconds = new(int64)
1164
+		*out.CompletionDeadlineSeconds = *in.CompletionDeadlineSeconds
1165
+	} else {
1166
+		out.CompletionDeadlineSeconds = nil
1167
+	}
1162 1168
 	return nil
1163 1169
 }
1164 1170
 
... ...
@@ -942,6 +942,12 @@ func deepCopy_v1_BuildSpec(in apiv1.BuildSpec, out *apiv1.BuildSpec, c *conversi
942 942
 	} else {
943 943
 		out.Resources = newVal.(pkgapiv1.ResourceRequirements)
944 944
 	}
945
+	if in.CompletionDeadlineSeconds != nil {
946
+		out.CompletionDeadlineSeconds = new(int64)
947
+		*out.CompletionDeadlineSeconds = *in.CompletionDeadlineSeconds
948
+	} else {
949
+		out.CompletionDeadlineSeconds = nil
950
+	}
945 951
 	return nil
946 952
 }
947 953
 
... ...
@@ -809,6 +809,12 @@ func convert_api_BuildSpec_To_v1beta3_BuildSpec(in *buildapi.BuildSpec, out *api
809 809
 	if err := convert_api_ResourceRequirements_To_v1beta3_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil {
810 810
 		return err
811 811
 	}
812
+	if in.CompletionDeadlineSeconds != nil {
813
+		out.CompletionDeadlineSeconds = new(int64)
814
+		*out.CompletionDeadlineSeconds = *in.CompletionDeadlineSeconds
815
+	} else {
816
+		out.CompletionDeadlineSeconds = nil
817
+	}
812 818
 	return nil
813 819
 }
814 820
 
... ...
@@ -1197,6 +1203,12 @@ func convert_v1beta3_BuildSpec_To_api_BuildSpec(in *apiv1beta3.BuildSpec, out *b
1197 1197
 	if err := convert_v1beta3_ResourceRequirements_To_api_ResourceRequirements(&in.Resources, &out.Resources, s); err != nil {
1198 1198
 		return err
1199 1199
 	}
1200
+	if in.CompletionDeadlineSeconds != nil {
1201
+		out.CompletionDeadlineSeconds = new(int64)
1202
+		*out.CompletionDeadlineSeconds = *in.CompletionDeadlineSeconds
1203
+	} else {
1204
+		out.CompletionDeadlineSeconds = nil
1205
+	}
1200 1206
 	return nil
1201 1207
 }
1202 1208
 
... ...
@@ -950,6 +950,12 @@ func deepCopy_v1beta3_BuildSpec(in apiv1beta3.BuildSpec, out *apiv1beta3.BuildSp
950 950
 	} else {
951 951
 		out.Resources = newVal.(pkgapiv1beta3.ResourceRequirements)
952 952
 	}
953
+	if in.CompletionDeadlineSeconds != nil {
954
+		out.CompletionDeadlineSeconds = new(int64)
955
+		*out.CompletionDeadlineSeconds = *in.CompletionDeadlineSeconds
956
+	} else {
957
+		out.CompletionDeadlineSeconds = nil
958
+	}
953 959
 	return nil
954 960
 }
955 961
 
... ...
@@ -58,6 +58,11 @@ type BuildSpec struct {
58 58
 
59 59
 	// Compute resource requirements to execute the build
60 60
 	Resources kapi.ResourceRequirements
61
+
62
+	// Optional duration in seconds, counted from the time when a build pod gets
63
+	// scheduled in the system, that the build may be active on a node before the
64
+	// system actively tries to terminate the build; value must be positive integer
65
+	CompletionDeadlineSeconds *int64
61 66
 }
62 67
 
63 68
 // BuildStatus contains the status of a build
... ...
@@ -42,6 +42,11 @@ type BuildSpec struct {
42 42
 
43 43
 	// Compute resource requirements to execute the build
44 44
 	Resources kapi.ResourceRequirements `json:"resources,omitempty" description:"the desired compute resources the build should have"`
45
+
46
+	// Optional duration in seconds, counted from the time when a build pod gets
47
+	// scheduled in the system, that the build may be active on a node before the
48
+	// system actively tries to terminate the build; value must be positive integer
49
+	CompletionDeadlineSeconds *int64 `json:"completionDeadlineSeconds,omitempty" description:"optional duration in seconds the build may be active on a node before the system will actively try to mark it failed and kill associated containers; value must be a positive integer"`
45 50
 }
46 51
 
47 52
 // BuildStatus contains the status of a build
... ...
@@ -42,6 +42,11 @@ type BuildSpec struct {
42 42
 
43 43
 	// Compute resource requirements to execute the build
44 44
 	Resources kapi.ResourceRequirements `json:"resources,omitempty" description:"the desired compute resources the build should have"`
45
+
46
+	// Optional duration in seconds, counted from the time when a build pod gets
47
+	// scheduled in the system, that the build may be active on a node before the
48
+	// system actively tries to terminate the build; value must be positive integer
49
+	CompletionDeadlineSeconds *int64 `json:"completionDeadlineSeconds,omitempty" description:"optional duration in seconds the build may be active on a node before the system will actively try to mark it failed and kill associated containers; value must be a positive integer"`
45 50
 }
46 51
 
47 52
 // BuildStatus contains the status of a build
... ...
@@ -125,6 +125,11 @@ func validateBuildSpec(spec *buildapi.BuildSpec) fielderrors.ValidationErrorList
125 125
 	if spec.Revision != nil {
126 126
 		allErrs = append(allErrs, validateRevision(spec.Revision).Prefix("revision")...)
127 127
 	}
128
+	if spec.CompletionDeadlineSeconds != nil {
129
+		if *spec.CompletionDeadlineSeconds <= 0 {
130
+			allErrs = append(allErrs, fielderrors.NewFieldInvalid("completionDeadlineSeconds", spec.CompletionDeadlineSeconds, "completionDeadlineSeconds must be a positive integer greater than 0"))
131
+		}
132
+	}
128 133
 
129 134
 	allErrs = append(allErrs, validateOutput(&spec.Output).Prefix("output")...)
130 135
 	allErrs = append(allErrs, validateStrategy(&spec.Strategy).Prefix("strategy")...)
... ...
@@ -567,6 +567,7 @@ func TestValidateSource(t *testing.T) {
567 567
 }
568 568
 
569 569
 func TestValidateBuildSpec(t *testing.T) {
570
+	zero := int64(0)
570 571
 	longString := strings.Repeat("1234567890", 100*61)
571 572
 	shortString := "FROM foo"
572 573
 	errorCases := []struct {
... ...
@@ -873,6 +874,7 @@ func TestValidateBuildSpec(t *testing.T) {
873 873
 				},
874 874
 			},
875 875
 		},
876
+		// 13
876 877
 		{
877 878
 			string(fielderrors.ValidationErrorTypeInvalid) + "source.dockerfile",
878 879
 			&buildapi.BuildSpec{
... ...
@@ -886,6 +888,7 @@ func TestValidateBuildSpec(t *testing.T) {
886 886
 				},
887 887
 			},
888 888
 		},
889
+		// 14
889 890
 		{
890 891
 			string(fielderrors.ValidationErrorTypeInvalid) + "source.dockerfile",
891 892
 			&buildapi.BuildSpec{
... ...
@@ -902,6 +905,31 @@ func TestValidateBuildSpec(t *testing.T) {
902 902
 				},
903 903
 			},
904 904
 		},
905
+		// 15
906
+		// invalid because CompletionDeadlineSeconds <= 0
907
+		{
908
+			string(fielderrors.ValidationErrorTypeInvalid) + "completionDeadlineSeconds",
909
+			&buildapi.BuildSpec{
910
+				Source: buildapi.BuildSource{
911
+					Type: buildapi.BuildSourceGit,
912
+					Git: &buildapi.GitBuildSource{
913
+						URI: "http://github.com/my/repository",
914
+					},
915
+					ContextDir: "context",
916
+				},
917
+				Strategy: buildapi.BuildStrategy{
918
+					Type:           buildapi.DockerBuildStrategyType,
919
+					DockerStrategy: &buildapi.DockerBuildStrategy{},
920
+				},
921
+				Output: buildapi.BuildOutput{
922
+					To: &kapi.ObjectReference{
923
+						Kind: "DockerImage",
924
+						Name: "repository/data",
925
+					},
926
+				},
927
+				CompletionDeadlineSeconds: &zero,
928
+			},
929
+		},
905 930
 	}
906 931
 
907 932
 	for count, config := range errorCases {
... ...
@@ -70,6 +70,9 @@ func (bs *CustomBuildStrategy) CreateBuildPod(build *buildapi.Build) (*kapi.Pod,
70 70
 			RestartPolicy: kapi.RestartPolicyNever,
71 71
 		},
72 72
 	}
73
+	if build.Spec.CompletionDeadlineSeconds != nil {
74
+		pod.Spec.ActiveDeadlineSeconds = build.Spec.CompletionDeadlineSeconds
75
+	}
73 76
 
74 77
 	if err := setupBuildEnv(build, pod); err != nil {
75 78
 		return nil, err
... ...
@@ -51,6 +51,9 @@ func TestCustomCreateBuildPod(t *testing.T) {
51 51
 	if len(container.VolumeMounts) != 3 {
52 52
 		t.Fatalf("Expected 3 volumes in container, got %d", len(container.VolumeMounts))
53 53
 	}
54
+	if *actual.Spec.ActiveDeadlineSeconds != 60 {
55
+		t.Errorf("Expected ActiveDeadlineSeconds 60, got %d", *actual.Spec.ActiveDeadlineSeconds)
56
+	}
54 57
 	for i, expected := range []string{dockerSocketPath, DockerPushSecretMountPath, sourceSecretMountPath} {
55 58
 		if container.VolumeMounts[i].MountPath != expected {
56 59
 			t.Fatalf("Expected %s in VolumeMount[%d], got %s", expected, i, container.VolumeMounts[i].MountPath)
... ...
@@ -83,19 +86,26 @@ func TestCustomCreateBuildPod(t *testing.T) {
83 83
 			t.Errorf("Expected %s variable to be set", name)
84 84
 		}
85 85
 	}
86
+}
86 87
 
87
-	expectedForcePull := mockCustomBuild(true)
88
-	actualForcePull, fperr := strategy.CreateBuildPod(expectedForcePull)
88
+func TestCustomCreateBuildPodExpectedForcePull(t *testing.T) {
89
+	strategy := CustomBuildStrategy{
90
+		Codec: latest.Codec,
91
+	}
92
+
93
+	expected := mockCustomBuild(true)
94
+	actual, fperr := strategy.CreateBuildPod(expected)
89 95
 	if fperr != nil {
90 96
 		t.Fatalf("Unexpected error: %v", fperr)
91 97
 	}
92
-	containerForcePull := actualForcePull.Spec.Containers[0]
93
-	if containerForcePull.ImagePullPolicy != kapi.PullAlways {
98
+	container := actual.Spec.Containers[0]
99
+	if container.ImagePullPolicy != kapi.PullAlways {
94 100
 		t.Errorf("Expected %v, got %v", kapi.PullAlways, container.ImagePullPolicy)
95 101
 	}
96 102
 }
97 103
 
98 104
 func mockCustomBuild(forcePull bool) *buildapi.Build {
105
+	timeout := int64(60)
99 106
 	return &buildapi.Build{
100 107
 		ObjectMeta: kapi.ObjectMeta{
101 108
 			Name: "customBuild",
... ...
@@ -143,6 +153,7 @@ func mockCustomBuild(forcePull bool) *buildapi.Build {
143 143
 					kapi.ResourceName(kapi.ResourceMemory): resource.MustParse("10G"),
144 144
 				},
145 145
 			},
146
+			CompletionDeadlineSeconds: &timeout,
146 147
 		},
147 148
 		Status: buildapi.BuildStatus{
148 149
 			Phase: buildapi.BuildPhaseNew,
... ...
@@ -67,6 +67,9 @@ func (bs *DockerBuildStrategy) CreateBuildPod(build *buildapi.Build) (*kapi.Pod,
67 67
 	}
68 68
 	pod.Spec.Containers[0].ImagePullPolicy = kapi.PullIfNotPresent
69 69
 	pod.Spec.Containers[0].Resources = build.Spec.Resources
70
+	if build.Spec.CompletionDeadlineSeconds != nil {
71
+		pod.Spec.ActiveDeadlineSeconds = build.Spec.CompletionDeadlineSeconds
72
+	}
70 73
 
71 74
 	setupDockerSocket(pod)
72 75
 	setupDockerSecrets(pod, build.Spec.Output.PushSecret, strategy.PullSecret)
... ...
@@ -49,6 +49,9 @@ func TestDockerCreateBuildPod(t *testing.T) {
49 49
 	if len(container.VolumeMounts) != 4 {
50 50
 		t.Fatalf("Expected 4 volumes in container, got %d", len(container.VolumeMounts))
51 51
 	}
52
+	if *actual.Spec.ActiveDeadlineSeconds != 60 {
53
+		t.Errorf("Expected ActiveDeadlineSeconds 60, got %d", *actual.Spec.ActiveDeadlineSeconds)
54
+	}
52 55
 	for i, expected := range []string{dockerSocketPath, DockerPushSecretMountPath, DockerPullSecretMountPath, sourceSecretMountPath} {
53 56
 		if container.VolumeMounts[i].MountPath != expected {
54 57
 			t.Fatalf("Expected %s in VolumeMount[%d], got %s", expected, i, container.VolumeMounts[i].MountPath)
... ...
@@ -89,6 +92,7 @@ func TestDockerCreateBuildPod(t *testing.T) {
89 89
 }
90 90
 
91 91
 func mockDockerBuild() *buildapi.Build {
92
+	timeout := int64(60)
92 93
 	return &buildapi.Build{
93 94
 		ObjectMeta: kapi.ObjectMeta{
94 95
 			Name: "dockerBuild",
... ...
@@ -131,6 +135,7 @@ func mockDockerBuild() *buildapi.Build {
131 131
 					kapi.ResourceName(kapi.ResourceMemory): resource.MustParse("10G"),
132 132
 				},
133 133
 			},
134
+			CompletionDeadlineSeconds: &timeout,
134 135
 		},
135 136
 		Status: buildapi.BuildStatus{
136 137
 			Phase: buildapi.BuildPhaseNew,
... ...
@@ -89,6 +89,9 @@ func (bs *SourceBuildStrategy) CreateBuildPod(build *buildapi.Build) (*kapi.Pod,
89 89
 	}
90 90
 	pod.Spec.Containers[0].ImagePullPolicy = kapi.PullIfNotPresent
91 91
 	pod.Spec.Containers[0].Resources = build.Spec.Resources
92
+	if build.Spec.CompletionDeadlineSeconds != nil {
93
+		pod.Spec.ActiveDeadlineSeconds = build.Spec.CompletionDeadlineSeconds
94
+	}
92 95
 
93 96
 	setupDockerSocket(pod)
94 97
 	setupDockerSecrets(pod, build.Spec.Output.PushSecret, strategy.PullSecret)
... ...
@@ -96,6 +96,9 @@ func testSTICreateBuildPod(t *testing.T, rootAllowed bool) {
96 96
 	if len(actual.Spec.Volumes) != 4 {
97 97
 		t.Fatalf("Expected 4 volumes in Build pod, got %d", len(actual.Spec.Volumes))
98 98
 	}
99
+	if *actual.Spec.ActiveDeadlineSeconds != 60 {
100
+		t.Errorf("Expected ActiveDeadlineSeconds 60, got %d", *actual.Spec.ActiveDeadlineSeconds)
101
+	}
99 102
 	if !kapi.Semantic.DeepEqual(container.Resources, expected.Spec.Resources) {
100 103
 		t.Fatalf("Expected actual=expected, %v != %v", container.Resources, expected.Spec.Resources)
101 104
 	}
... ...
@@ -137,6 +140,7 @@ func testSTICreateBuildPod(t *testing.T, rootAllowed bool) {
137 137
 }
138 138
 
139 139
 func mockSTIBuild() *buildapi.Build {
140
+	timeout := int64(60)
140 141
 	return &buildapi.Build{
141 142
 		ObjectMeta: kapi.ObjectMeta{
142 143
 			Name: "stiBuild",
... ...
@@ -184,6 +188,7 @@ func mockSTIBuild() *buildapi.Build {
184 184
 					kapi.ResourceName(kapi.ResourceMemory): resource.MustParse("10G"),
185 185
 				},
186 186
 			},
187
+			CompletionDeadlineSeconds: &timeout,
187 188
 		},
188 189
 		Status: buildapi.BuildStatus{
189 190
 			Phase: buildapi.BuildPhaseNew,
... ...
@@ -314,12 +314,13 @@ func (g *BuildGenerator) generateBuildFromConfig(ctx kapi.Context, bc *buildapi.
314 314
 	bcCopy := obj.(*buildapi.BuildConfig)
315 315
 	build := &buildapi.Build{
316 316
 		Spec: buildapi.BuildSpec{
317
-			ServiceAccount: serviceAccount,
318
-			Source:         bcCopy.Spec.Source,
319
-			Strategy:       bcCopy.Spec.Strategy,
320
-			Output:         bcCopy.Spec.Output,
321
-			Revision:       revision,
322
-			Resources:      bcCopy.Spec.Resources,
317
+			ServiceAccount:            serviceAccount,
318
+			Source:                    bcCopy.Spec.Source,
319
+			Strategy:                  bcCopy.Spec.Strategy,
320
+			Output:                    bcCopy.Spec.Output,
321
+			Revision:                  revision,
322
+			Resources:                 bcCopy.Spec.Resources,
323
+			CompletionDeadlineSeconds: bcCopy.Spec.CompletionDeadlineSeconds,
323 324
 		},
324 325
 		ObjectMeta: kapi.ObjectMeta{
325 326
 			Labels: bcCopy.Labels,
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"strconv"
9 9
 	"strings"
10 10
 	"text/tabwriter"
11
+	"time"
11 12
 
12 13
 	"github.com/docker/docker/pkg/units"
13 14
 
... ...
@@ -249,6 +250,10 @@ func describeBuildSpec(p buildapi.BuildSpec, out *tabwriter.Writer) {
249 249
 			formatString(out, "Revision Message", p.Revision.Git.Message)
250 250
 		}
251 251
 	}
252
+
253
+	if p.CompletionDeadlineSeconds != nil {
254
+		formatString(out, "Fail Build After", time.Duration(*p.CompletionDeadlineSeconds)*time.Second)
255
+	}
252 256
 }
253 257
 
254 258
 func describeSourceStrategy(s *buildapi.SourceBuildStrategy, out *tabwriter.Writer) {
255 259
new file mode 100644
... ...
@@ -0,0 +1,84 @@
0
+package builds
1
+
2
+import (
3
+	kapi "k8s.io/kubernetes/pkg/api"
4
+	"k8s.io/kubernetes/pkg/fields"
5
+	"k8s.io/kubernetes/pkg/labels"
6
+
7
+	g "github.com/onsi/ginkgo"
8
+	o "github.com/onsi/gomega"
9
+
10
+	buildapi "github.com/openshift/origin/pkg/build/api"
11
+	buildutil "github.com/openshift/origin/pkg/build/util"
12
+	exutil "github.com/openshift/origin/test/extended/util"
13
+)
14
+
15
+var _ = g.Describe("builds: build with CompletionDeadlineSeconds", func() {
16
+	defer g.GinkgoRecover()
17
+	var (
18
+		sourceFixture = exutil.FixturePath("..", "extended", "fixtures", "test-cds-sourcebuild.json")
19
+		dockerFixture = exutil.FixturePath("..", "extended", "fixtures", "test-cds-dockerbuild.json")
20
+		oc            = exutil.NewCLI("cli-start-build", exutil.KubeConfigPath())
21
+	)
22
+
23
+	g.JustBeforeEach(func() {
24
+		g.By("waiting for builder service account")
25
+		err := exutil.WaitForBuilderAccount(oc.KubeREST().ServiceAccounts(oc.Namespace()))
26
+		o.Expect(err).NotTo(o.HaveOccurred())
27
+	})
28
+
29
+	g.Describe("oc start-build source-build --wait", func() {
30
+		g.It("Source: should start a build and wait for the build failed and build pod being killed by kubelet", func() {
31
+
32
+			g.By("calling oc create source-build")
33
+			err := oc.Run("create").Args("-f", sourceFixture).Execute()
34
+			o.Expect(err).NotTo(o.HaveOccurred())
35
+
36
+			g.By("starting the build with --wait flag")
37
+			_, err = oc.Run("start-build").Args("source-build", "--wait").Output()
38
+			o.Expect(err).To(o.HaveOccurred())
39
+
40
+			g.By("verifying the build status")
41
+			builds, err := oc.REST().Builds(oc.Namespace()).List(labels.Everything(), fields.Everything())
42
+			o.Expect(err).NotTo(o.HaveOccurred())
43
+			o.Expect(builds.Items).To(o.HaveLen(1))
44
+
45
+			build := builds.Items[0]
46
+			o.Expect(build.Status.Phase).Should(o.BeEquivalentTo(buildapi.BuildPhaseFailed))
47
+
48
+			g.By("verifying the build pod status")
49
+			pod, err := oc.KubeREST().Pods(oc.Namespace()).Get(buildutil.GetBuildPodName(&build))
50
+			o.Expect(err).NotTo(o.HaveOccurred())
51
+			o.Expect(pod.Status.Phase).Should(o.BeEquivalentTo(kapi.PodFailed))
52
+			o.Expect(pod.Status.Reason).Should(o.ContainSubstring("DeadlineExceeded"))
53
+		})
54
+	})
55
+
56
+	g.Describe("oc start-build docker-build --wait", func() {
57
+		g.It("Docker: should start a build and wait for the build failed and build pod being killed by kubelet", func() {
58
+
59
+			g.By("calling oc create docker-build")
60
+			err := oc.Run("create").Args("-f", dockerFixture).Execute()
61
+			o.Expect(err).NotTo(o.HaveOccurred())
62
+
63
+			g.By("starting the build with --wait flag")
64
+			_, err = oc.Run("start-build").Args("docker-build", "--wait").Output()
65
+			o.Expect(err).To(o.HaveOccurred())
66
+
67
+			g.By("verifying the build status")
68
+			builds, err := oc.REST().Builds(oc.Namespace()).List(labels.Everything(), fields.Everything())
69
+			o.Expect(err).NotTo(o.HaveOccurred())
70
+			o.Expect(builds.Items).To(o.HaveLen(1))
71
+
72
+			build := builds.Items[0]
73
+			o.Expect(build.Status.Phase).Should(o.BeEquivalentTo(buildapi.BuildPhaseFailed))
74
+
75
+			g.By("verifying the build pod status")
76
+			pod, err := oc.KubeREST().Pods(oc.Namespace()).Get(buildutil.GetBuildPodName(&build))
77
+			o.Expect(err).NotTo(o.HaveOccurred())
78
+			o.Expect(pod.Status.Phase).Should(o.BeEquivalentTo(kapi.PodFailed))
79
+			o.Expect(pod.Status.Reason).Should(o.ContainSubstring("DeadlineExceeded"))
80
+		})
81
+	})
82
+
83
+})
0 84
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+{
1
+  "kind":"BuildConfig",
2
+  "apiVersion":"v1",
3
+  "metadata":{
4
+    "name":"docker-build"
5
+  },
6
+  "spec":{
7
+    "completionDeadlineSeconds": 5,
8
+    "triggers":[],
9
+    "source":{
10
+      "type":"Git",
11
+      "git":{
12
+        "uri":"https://github.com/openshift/origin"
13
+      },
14
+      "contextDir":"test/extended/fixtures/test-build-app"
15
+    },
16
+    "strategy":{
17
+      "type":"Docker",
18
+      "dockerStrategy":{
19
+        "from":{
20
+          "kind":"DockerImage",
21
+          "name":"openshift/ruby-20-centos7"
22
+        }
23
+      }
24
+    }
25
+  }
26
+}
0 27
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+{
1
+  "kind": "List",
2
+  "apiVersion": "v1",
3
+  "metadata": {},
4
+  "items": [
5
+    {
6
+      "kind": "ImageStream",
7
+      "apiVersion": "v1",
8
+      "metadata": {
9
+        "name": "origin-ruby-sample"
10
+      },
11
+      "spec": {},
12
+      "status": {
13
+        "dockerImageRepository": ""
14
+      }
15
+    },
16
+    {
17
+      "kind": "BuildConfig",
18
+      "apiVersion": "v1",
19
+      "metadata": {
20
+        "name": "source-build"
21
+      },
22
+      "spec": {
23
+        "completionDeadlineSeconds": 5,
24
+        "triggers": [],
25
+        "source": {
26
+          "type": "Git",
27
+          "git": {
28
+            "uri": "git://github.com/openshift/ruby-hello-world.git"
29
+          }
30
+        },
31
+        "strategy": {
32
+          "type": "Source",
33
+          "sourceStrategy": {
34
+            "from": {
35
+              "kind": "DockerImage",
36
+              "name": "openshift/ruby-20-centos7"
37
+            }
38
+          }
39
+        }
40
+      }
41
+    }
42
+  ]
43
+}