Browse code

Allow to specify the run policy for builds

Michal Fojtik authored on 2016/05/02 17:06:20
Showing 45 changed files
... ...
@@ -17512,6 +17512,10 @@
17512 17512
       },
17513 17513
       "description": "triggers determine how new Builds can be launched from a BuildConfig. If no triggers are defined, a new build can only occur as a result of an explicit client build creation."
17514 17514
      },
17515
+     "runPolicy": {
17516
+      "type": "string",
17517
+      "description": "RunPolicy describes how the new build created from this build configuration will be scheduled for execution. This is optional, if not specified we default to \"Serial\"."
17518
+     },
17515 17519
      "serviceAccount": {
17516 17520
       "type": "string",
17517 17521
       "description": "serviceAccount is the name of the ServiceAccount to use to run the pod created by this build. The pod will be allowed to use secrets referenced by the ServiceAccount"
... ...
@@ -770,6 +770,7 @@ func deepCopy_api_BuildConfigSpec(in buildapi.BuildConfigSpec, out *buildapi.Bui
770 770
 	} else {
771 771
 		out.Triggers = nil
772 772
 	}
773
+	out.RunPolicy = in.RunPolicy
773 774
 	if err := deepCopy_api_BuildSpec(in.BuildSpec, &out.BuildSpec, c); err != nil {
774 775
 		return err
775 776
 	}
... ...
@@ -197,6 +197,10 @@ func fuzzInternalObject(t *testing.T, forVersion unversioned.GroupVersion, item
197 197
 				j.From.Kind = specs[c.Intn(len(specs))]
198 198
 			}
199 199
 		},
200
+		func(j *build.BuildConfigSpec, c fuzz.Continue) {
201
+			c.FuzzNoCustom(j)
202
+			j.RunPolicy = build.BuildRunPolicySerial
203
+		},
200 204
 		func(j *build.SourceBuildStrategy, c fuzz.Continue) {
201 205
 			c.FuzzNoCustom(j)
202 206
 			j.From.Kind = "ImageStreamTag"
... ...
@@ -1041,6 +1041,7 @@ func autoConvert_api_BuildConfigSpec_To_v1_BuildConfigSpec(in *buildapi.BuildCon
1041 1041
 	} else {
1042 1042
 		out.Triggers = nil
1043 1043
 	}
1044
+	out.RunPolicy = v1.BuildRunPolicy(in.RunPolicy)
1044 1045
 	if err := Convert_api_BuildSpec_To_v1_BuildSpec(&in.BuildSpec, &out.BuildSpec, s); err != nil {
1045 1046
 		return err
1046 1047
 	}
... ...
@@ -1906,6 +1907,7 @@ func autoConvert_v1_BuildConfigSpec_To_api_BuildConfigSpec(in *v1.BuildConfigSpe
1906 1906
 	} else {
1907 1907
 		out.Triggers = nil
1908 1908
 	}
1909
+	out.RunPolicy = buildapi.BuildRunPolicy(in.RunPolicy)
1909 1910
 	if err := Convert_v1_BuildSpec_To_api_BuildSpec(&in.BuildSpec, &out.BuildSpec, s); err != nil {
1910 1911
 		return err
1911 1912
 	}
... ...
@@ -790,6 +790,7 @@ func deepCopy_v1_BuildConfigSpec(in apiv1.BuildConfigSpec, out *apiv1.BuildConfi
790 790
 	} else {
791 791
 		out.Triggers = nil
792 792
 	}
793
+	out.RunPolicy = in.RunPolicy
793 794
 	if err := deepCopy_v1_BuildSpec(in.BuildSpec, &out.BuildSpec, c); err != nil {
794 795
 		return err
795 796
 	}
... ...
@@ -1049,6 +1049,7 @@ func autoConvert_api_BuildConfigSpec_To_v1beta3_BuildConfigSpec(in *buildapi.Bui
1049 1049
 	} else {
1050 1050
 		out.Triggers = nil
1051 1051
 	}
1052
+	out.RunPolicy = v1beta3.BuildRunPolicy(in.RunPolicy)
1052 1053
 	if err := Convert_api_BuildSpec_To_v1beta3_BuildSpec(&in.BuildSpec, &out.BuildSpec, s); err != nil {
1053 1054
 		return err
1054 1055
 	}
... ...
@@ -1812,6 +1813,7 @@ func autoConvert_v1beta3_BuildConfigSpec_To_api_BuildConfigSpec(in *v1beta3.Buil
1812 1812
 	} else {
1813 1813
 		out.Triggers = nil
1814 1814
 	}
1815
+	out.RunPolicy = buildapi.BuildRunPolicy(in.RunPolicy)
1815 1816
 	if err := Convert_v1beta3_BuildSpec_To_api_BuildSpec(&in.BuildSpec, &out.BuildSpec, s); err != nil {
1816 1817
 		return err
1817 1818
 	}
... ...
@@ -798,6 +798,7 @@ func deepCopy_v1beta3_BuildConfigSpec(in apiv1beta3.BuildConfigSpec, out *apiv1b
798 798
 	} else {
799 799
 		out.Triggers = nil
800 800
 	}
801
+	out.RunPolicy = in.RunPolicy
801 802
 	if err := deepCopy_v1beta3_BuildSpec(in.BuildSpec, &out.BuildSpec, c); err != nil {
802 803
 		return err
803 804
 	}
... ...
@@ -19,6 +19,8 @@ const (
19 19
 	BuildPodNameAnnotation = "openshift.io/build.pod-name"
20 20
 	// BuildLabel is the key of a Pod label whose value is the Name of a Build which is run.
21 21
 	BuildLabel = "openshift.io/build.name"
22
+	// BuildRunPolicyLabel represents the start policy used to to start the build.
23
+	BuildRunPolicyLabel = "openshift.io/build.start-policy"
22 24
 	// DefaultDockerLabelNamespace is the key of a Build label, whose values are build metadata.
23 25
 	DefaultDockerLabelNamespace = "io.openshift."
24 26
 	// OriginVersion is an environment variable key that indicates the version of origin that
... ...
@@ -30,6 +32,15 @@ const (
30 30
 	// DropCapabilities is an environment variable that contains a list of capabilities to drop when
31 31
 	// executing a Source build
32 32
 	DropCapabilities = "DROP_CAPS"
33
+	// BuildConfigLabel is the key of a Build label whose value is the ID of a BuildConfig
34
+	// on which the Build is based.
35
+	BuildConfigLabel = "openshift.io/build-config.name"
36
+	// BuildConfigLabelDeprecated was used as BuildConfigLabel before adding namespaces.
37
+	// We keep it for backward compatibility.
38
+	BuildConfigLabelDeprecated = "buildconfig"
39
+	// BuildConfigPausedAnnotation is an annotation that marks a BuildConfig as paused.
40
+	// New Builds cannot be instantiated from a paused BuildConfig.
41
+	BuildConfigPausedAnnotation = "openshift.io/build-config.paused"
33 42
 )
34 43
 
35 44
 // Build encapsulates the inputs needed to produce a new deployable image, as well as
... ...
@@ -545,18 +556,6 @@ type BuildOutput struct {
545 545
 	PushSecret *kapi.LocalObjectReference
546 546
 }
547 547
 
548
-const (
549
-	// BuildConfigLabel is the key of a Build label whose value is the ID of a BuildConfig
550
-	// on which the Build is based.
551
-	BuildConfigLabel = "openshift.io/build-config.name"
552
-	// BuildConfigLabelDeprecated was used as BuildConfigLabel before adding namespaces.
553
-	// We keep it for backward compatibility.
554
-	BuildConfigLabelDeprecated = "buildconfig"
555
-	// BuildConfigPausedAnnotation is an annotation that marks a BuildConfig as paused.
556
-	// New Builds cannot be instantiated from a paused BuildConfig.
557
-	BuildConfigPausedAnnotation = "openshift.io/build-config.paused"
558
-)
559
-
560 548
 // BuildConfig is a template which can be used to create new builds.
561 549
 type BuildConfig struct {
562 550
 	unversioned.TypeMeta
... ...
@@ -575,10 +574,34 @@ type BuildConfigSpec struct {
575 575
 	// are defined, a new build can only occur as a result of an explicit client build creation.
576 576
 	Triggers []BuildTriggerPolicy
577 577
 
578
+	// RunPolicy describes how the new build created from this build
579
+	// configuration will be scheduled for execution.
580
+	// This is optional, if not specified we default to "Serial".
581
+	RunPolicy BuildRunPolicy
582
+
578 583
 	// BuildSpec is the desired build specification
579 584
 	BuildSpec
580 585
 }
581 586
 
587
+// BuildRunPolicy defines the behaviour of how the new builds are executed
588
+// from the existing build configuration.
589
+type BuildRunPolicy string
590
+
591
+const (
592
+	// BuildRunPolicyParallel schedules new builds immediately after they are
593
+	// created. Builds will be executed in parallel.
594
+	BuildRunPolicyParallel BuildRunPolicy = "Parallel"
595
+
596
+	// BuildRunPolicySerial schedules new builds to execute in a sequence as
597
+	// they are created. Every build gets queued up and will execute when the
598
+	// previous build completes. This is the default policy.
599
+	BuildRunPolicySerial BuildRunPolicy = "Serial"
600
+
601
+	// BuildRunPolicySerialLatestOnly schedules only the latest build to execute,
602
+	// cancelling all the previously queued build.
603
+	BuildRunPolicySerialLatestOnly BuildRunPolicy = "SerialLatestOnly"
604
+)
605
+
582 606
 // BuildConfigStatus contains current state of the build config object.
583 607
 type BuildConfigStatus struct {
584 608
 	// LastVersion is used to inform about number of last triggered build.
... ...
@@ -218,6 +218,11 @@ func Convert_v1_BuildStrategy_To_api_BuildStrategy(in *BuildStrategy, out *newer
218 218
 
219 219
 func addConversionFuncs(scheme *runtime.Scheme) {
220 220
 	err := scheme.AddDefaultingFuncs(
221
+		func(config *BuildConfigSpec) {
222
+			if len(config.RunPolicy) == 0 {
223
+				config.RunPolicy = BuildRunPolicySerial
224
+			}
225
+		},
221 226
 		func(source *BuildSource) {
222 227
 			if (source != nil) && (source.Type == BuildSourceBinary) && (source.Binary == nil) {
223 228
 				source.Binary = &BinaryBuildSource{}
... ...
@@ -63,8 +63,9 @@ func (BuildConfigList) SwaggerDoc() map[string]string {
63 63
 }
64 64
 
65 65
 var map_BuildConfigSpec = map[string]string{
66
-	"":         "BuildConfigSpec describes when and how builds are created",
67
-	"triggers": "triggers determine how new Builds can be launched from a BuildConfig. If no triggers are defined, a new build can only occur as a result of an explicit client build creation.",
66
+	"":          "BuildConfigSpec describes when and how builds are created",
67
+	"triggers":  "triggers determine how new Builds can be launched from a BuildConfig. If no triggers are defined, a new build can only occur as a result of an explicit client build creation.",
68
+	"runPolicy": "RunPolicy describes how the new build created from this build configuration will be scheduled for execution. This is optional, if not specified we default to \"Serial\".",
68 69
 }
69 70
 
70 71
 func (BuildConfigSpec) SwaggerDoc() map[string]string {
... ...
@@ -546,10 +546,34 @@ type BuildConfigSpec struct {
546 546
 	// are defined, a new build can only occur as a result of an explicit client build creation.
547 547
 	Triggers []BuildTriggerPolicy `json:"triggers"`
548 548
 
549
+	// RunPolicy describes how the new build created from this build
550
+	// configuration will be scheduled for execution.
551
+	// This is optional, if not specified we default to "Serial".
552
+	RunPolicy BuildRunPolicy `json:"runPolicy,omitempty"`
553
+
549 554
 	// BuildSpec is the desired build specification
550 555
 	BuildSpec `json:",inline"`
551 556
 }
552 557
 
558
+// BuildRunPolicy defines the behaviour of how the new builds are executed
559
+// from the existing build configuration.
560
+type BuildRunPolicy string
561
+
562
+const (
563
+	// BuildRunPolicyParallel schedules new builds immediately after they are
564
+	// created. Builds will be executed in parallel.
565
+	BuildRunPolicyParallel BuildRunPolicy = "Parallel"
566
+
567
+	// BuildRunPolicySerial schedules new builds to execute in a sequence as
568
+	// they are created. Every build gets queued up and will execute when the
569
+	// previous build completes. This is the default policy.
570
+	BuildRunPolicySerial BuildRunPolicy = "Serial"
571
+
572
+	// BuildRunPolicySerialLatestOnly schedules only the latest build to execute,
573
+	// cancelling all the previously queued build.
574
+	BuildRunPolicySerialLatestOnly BuildRunPolicy = "SerialLatestOnly"
575
+)
576
+
553 577
 // BuildConfigStatus contains current state of the build config object.
554 578
 type BuildConfigStatus struct {
555 579
 	// lastVersion is used to inform about number of last triggered build.
... ...
@@ -220,6 +220,11 @@ func Convert_api_BuildStrategy_To_v1beta3_BuildStrategy(in *newer.BuildStrategy,
220 220
 
221 221
 func addConversionFuncs(scheme *runtime.Scheme) {
222 222
 	err := scheme.AddDefaultingFuncs(
223
+		func(config *BuildConfigSpec) {
224
+			if len(config.RunPolicy) == 0 {
225
+				config.RunPolicy = BuildRunPolicySerial
226
+			}
227
+		},
223 228
 		func(strategy *BuildStrategy) {
224 229
 			if (strategy != nil) && (strategy.Type == DockerBuildStrategyType) {
225 230
 				//  initialize DockerStrategy to a default state if it's not set.
... ...
@@ -510,9 +510,33 @@ type BuildConfigSpec struct {
510 510
 	// are defined, a new build can only occur as a result of an explicit client build creation.
511 511
 	Triggers []BuildTriggerPolicy `json:"triggers"`
512 512
 
513
+	// RunPolicy describes how the new build created from this build
514
+	// configuration will be scheduled for execution.
515
+	// This is optional, if not specified we default to "Serial".
516
+	RunPolicy BuildRunPolicy `json:"runPolicy,omitempty"`
517
+
513 518
 	BuildSpec `json:",inline"`
514 519
 }
515 520
 
521
+// BuildRunPolicy defines the behaviour of how the new builds are executed
522
+// from the existing build configuration.
523
+type BuildRunPolicy string
524
+
525
+const (
526
+	// BuildRunPolicyParallel schedules new builds immediately after they are
527
+	// created. Builds will be executed in parallel.
528
+	BuildRunPolicyParallel BuildRunPolicy = "Parallel"
529
+
530
+	// BuildRunPolicySerial schedules new builds to execute in a sequence as
531
+	// they are created. Every build gets queued up and will execute when the
532
+	// previous build completes. This is the default policy.
533
+	BuildRunPolicySerial BuildRunPolicy = "Serial"
534
+
535
+	// BuildRunPolicySerialLatestOnly schedules only the latest build to execute,
536
+	// cancelling all the previously queued build.
537
+	BuildRunPolicySerialLatestOnly BuildRunPolicy = "SerialLatestOnly"
538
+)
539
+
516 540
 // BuildConfigStatus contains current state of the build config object.
517 541
 type BuildConfigStatus struct {
518 542
 	// LastVersion is used to inform about number of last triggered build.
... ...
@@ -82,6 +82,13 @@ func ValidateBuildConfig(config *buildapi.BuildConfig) field.ErrorList {
82 82
 		fromRefs[fromKey] = struct{}{}
83 83
 	}
84 84
 
85
+	switch config.Spec.RunPolicy {
86
+	case buildapi.BuildRunPolicyParallel, buildapi.BuildRunPolicySerial, buildapi.BuildRunPolicySerialLatestOnly:
87
+	default:
88
+		allErrs = append(allErrs, field.Invalid(specPath.Child("runPolicy"), config.Spec.RunPolicy,
89
+			"run policy must Parallel, Serial, or SerialLatestOnly"))
90
+	}
91
+
85 92
 	allErrs = append(allErrs, validateBuildSpec(&config.Spec.BuildSpec, specPath)...)
86 93
 
87 94
 	return allErrs
... ...
@@ -137,6 +137,7 @@ func TestBuildConfigEmptySource(t *testing.T) {
137 137
 		{
138 138
 			ObjectMeta: kapi.ObjectMeta{Name: "config-id", Namespace: "namespace"},
139 139
 			Spec: buildapi.BuildConfigSpec{
140
+				RunPolicy: buildapi.BuildRunPolicySerial,
140 141
 				BuildSpec: buildapi.BuildSpec{
141 142
 					Source: buildapi.BuildSource{},
142 143
 					Strategy: buildapi.BuildStrategy{
... ...
@@ -159,6 +160,7 @@ func TestBuildConfigEmptySource(t *testing.T) {
159 159
 		{
160 160
 			ObjectMeta: kapi.ObjectMeta{Name: "config-id", Namespace: "namespace"},
161 161
 			Spec: buildapi.BuildConfigSpec{
162
+				RunPolicy: buildapi.BuildRunPolicySerial,
162 163
 				BuildSpec: buildapi.BuildSpec{
163 164
 					Source: buildapi.BuildSource{},
164 165
 					Strategy: buildapi.BuildStrategy{
... ...
@@ -188,6 +190,7 @@ func TestBuildConfigEmptySource(t *testing.T) {
188 188
 	badBuildConfig := buildapi.BuildConfig{
189 189
 		ObjectMeta: kapi.ObjectMeta{Name: "config-id", Namespace: "namespace"},
190 190
 		Spec: buildapi.BuildConfigSpec{
191
+			RunPolicy: buildapi.BuildRunPolicySerial,
191 192
 			BuildSpec: buildapi.BuildSpec{
192 193
 				Source: buildapi.BuildSource{},
193 194
 				Strategy: buildapi.BuildStrategy{
... ...
@@ -384,6 +387,7 @@ func TestBuildConfigGitSourceWithProxyFailure(t *testing.T) {
384 384
 	buildConfig := &buildapi.BuildConfig{
385 385
 		ObjectMeta: kapi.ObjectMeta{Name: "config-id", Namespace: "namespace"},
386 386
 		Spec: buildapi.BuildConfigSpec{
387
+			RunPolicy: buildapi.BuildRunPolicySerial,
387 388
 			BuildSpec: buildapi.BuildSpec{
388 389
 				Source: buildapi.BuildSource{
389 390
 					Git: &buildapi.GitBuildSource{
... ...
@@ -424,6 +428,7 @@ func TestBuildConfigDockerStrategyImageChangeTrigger(t *testing.T) {
424 424
 	buildConfig := &buildapi.BuildConfig{
425 425
 		ObjectMeta: kapi.ObjectMeta{Name: "config-id", Namespace: "namespace"},
426 426
 		Spec: buildapi.BuildConfigSpec{
427
+			RunPolicy: buildapi.BuildRunPolicySerial,
427 428
 			BuildSpec: buildapi.BuildSpec{
428 429
 				Source: buildapi.BuildSource{
429 430
 					Git: &buildapi.GitBuildSource{
... ...
@@ -467,6 +472,7 @@ func TestBuildConfigValidationFailureRequiredName(t *testing.T) {
467 467
 	buildConfig := &buildapi.BuildConfig{
468 468
 		ObjectMeta: kapi.ObjectMeta{Name: "", Namespace: "foo"},
469 469
 		Spec: buildapi.BuildConfigSpec{
470
+			RunPolicy: buildapi.BuildRunPolicySerial,
470 471
 			BuildSpec: buildapi.BuildSpec{
471 472
 				Source: buildapi.BuildSource{
472 473
 					Git: &buildapi.GitBuildSource{
... ...
@@ -738,6 +744,7 @@ func TestBuildConfigImageChangeTriggers(t *testing.T) {
738 738
 		buildConfig := &buildapi.BuildConfig{
739 739
 			ObjectMeta: kapi.ObjectMeta{Name: "bar", Namespace: "foo"},
740 740
 			Spec: buildapi.BuildConfigSpec{
741
+				RunPolicy: buildapi.BuildRunPolicySerial,
741 742
 				BuildSpec: buildapi.BuildSpec{
742 743
 					Source: buildapi.BuildSource{
743 744
 						Git: &buildapi.GitBuildSource{
... ...
@@ -782,6 +789,7 @@ func TestBuildConfigValidationOutputFailure(t *testing.T) {
782 782
 	buildConfig := &buildapi.BuildConfig{
783 783
 		ObjectMeta: kapi.ObjectMeta{Name: ""},
784 784
 		Spec: buildapi.BuildConfigSpec{
785
+			RunPolicy: buildapi.BuildRunPolicySerial,
785 786
 			BuildSpec: buildapi.BuildSpec{
786 787
 				Source: buildapi.BuildSource{
787 788
 					Git: &buildapi.GitBuildSource{
... ...
@@ -3,6 +3,7 @@ package client
3 3
 import (
4 4
 	buildapi "github.com/openshift/origin/pkg/build/api"
5 5
 	osclient "github.com/openshift/origin/pkg/client"
6
+	kapi "k8s.io/kubernetes/pkg/api"
6 7
 )
7 8
 
8 9
 // BuildConfigGetter provides methods for getting BuildConfigs
... ...
@@ -41,6 +42,11 @@ type BuildUpdater interface {
41 41
 	Update(namespace string, build *buildapi.Build) error
42 42
 }
43 43
 
44
+// BuildLister provides methods for listing the Builds.
45
+type BuildLister interface {
46
+	List(namespace string, opts kapi.ListOptions) (*buildapi.BuildList, error)
47
+}
48
+
44 49
 // OSClientBuildClient deletes build create and update operations to the OpenShift client interface
45 50
 type OSClientBuildClient struct {
46 51
 	Client osclient.Interface
... ...
@@ -57,6 +63,11 @@ func (c OSClientBuildClient) Update(namespace string, build *buildapi.Build) err
57 57
 	return e
58 58
 }
59 59
 
60
+// List lists the builds using the OpenShift client.
61
+func (c OSClientBuildClient) List(namespace string, opts kapi.ListOptions) (*buildapi.BuildList, error) {
62
+	return c.Client.Builds(namespace).List(opts)
63
+}
64
+
60 65
 // BuildCloner provides methods for cloning builds
61 66
 type BuildCloner interface {
62 67
 	Clone(namespace string, request *buildapi.BuildRequest) (*buildapi.Build, error)
... ...
@@ -13,6 +13,7 @@ import (
13 13
 
14 14
 	buildapi "github.com/openshift/origin/pkg/build/api"
15 15
 	buildclient "github.com/openshift/origin/pkg/build/client"
16
+	"github.com/openshift/origin/pkg/build/controller/policy"
16 17
 	strategy "github.com/openshift/origin/pkg/build/controller/strategy"
17 18
 	buildutil "github.com/openshift/origin/pkg/build/util"
18 19
 	imageapi "github.com/openshift/origin/pkg/image/api"
... ...
@@ -21,10 +22,12 @@ import (
21 21
 // BuildController watches build resources and manages their state
22 22
 type BuildController struct {
23 23
 	BuildUpdater      buildclient.BuildUpdater
24
+	BuildLister       buildclient.BuildLister
24 25
 	PodManager        podManager
25 26
 	BuildStrategy     BuildStrategy
26 27
 	ImageStreamClient imageStreamClient
27 28
 	Recorder          record.EventRecorder
29
+	RunPolicies       []policy.RunPolicy
28 30
 }
29 31
 
30 32
 // BuildStrategy knows how to create a pod spec for a pod which can execute a build.
... ...
@@ -79,25 +82,42 @@ func (bc *BuildController) CancelBuild(build *buildapi.Build) error {
79 79
 // HandleBuild deletes pods for cancelled builds and takes new builds and puts
80 80
 // them in the pending state after creating a corresponding pod
81 81
 func (bc *BuildController) HandleBuild(build *buildapi.Build) error {
82
-	glog.V(4).Infof("Handling build %s/%s", build.Namespace, build.Name)
82
+	// these builds are processed/updated/etc by the jenkins sync plugin
83
+	if build.Spec.Strategy.JenkinsPipelineStrategy != nil {
84
+		glog.V(4).Infof("Ignoring build with jenkins pipeline strategy")
85
+		return nil
86
+	}
87
+	glog.V(4).Infof("Handling build %s/%s (%s)", build.Namespace, build.Name, build.Status.Phase)
88
+
89
+	runPolicy := policy.ForBuild(build, bc.RunPolicies)
90
+	if runPolicy == nil {
91
+		return fmt.Errorf("unable to determine build scheduler for %s/%s", build.Namespace, build.Name)
92
+	}
93
+
94
+	if buildutil.IsBuildComplete(build) {
95
+		if err := runPolicy.OnComplete(build); err != nil {
96
+			return err
97
+		}
98
+		return nil
99
+	}
83 100
 
84 101
 	// A cancelling event was triggered for the build, delete its pod and update build status.
85 102
 	if build.Status.Cancelled && build.Status.Phase != buildapi.BuildPhaseCancelled {
103
+		glog.V(5).Infof("Marking build %s/%s as cancelled", build.Namespace, build.Name)
86 104
 		if err := bc.CancelBuild(build); err != nil {
87 105
 			build.Status.Reason = buildapi.StatusReasonCancelBuildFailed
88 106
 			return fmt.Errorf("Failed to cancel build %s/%s: %v, will retry", build.Namespace, build.Name, err)
89 107
 		}
90 108
 	}
91 109
 
92
-	// these builds are processed/updated/etc by the jenkins sync plugin
93
-	if build.Spec.Strategy.JenkinsPipelineStrategy != nil {
94
-		glog.V(4).Infof("Ignoring build with jenkins pipeline strategy")
110
+	// Handle only new builds from this point
111
+	if build.Status.Phase != buildapi.BuildPhaseNew {
95 112
 		return nil
96 113
 	}
97 114
 
98
-	// Handle new builds
99
-	if build.Status.Phase != buildapi.BuildPhaseNew {
100
-		return nil
115
+	// The runPolicy decides whether to execute this build or not.
116
+	if run, err := runPolicy.IsRunnable(build); err != nil || !run {
117
+		return err
101 118
 	}
102 119
 
103 120
 	if err := bc.nextBuildPhase(build); err != nil {
... ...
@@ -12,6 +12,7 @@ import (
12 12
 
13 13
 	buildapi "github.com/openshift/origin/pkg/build/api"
14 14
 	buildclient "github.com/openshift/origin/pkg/build/client"
15
+	"github.com/openshift/origin/pkg/build/controller/policy"
15 16
 	buildtest "github.com/openshift/origin/pkg/build/controller/test"
16 17
 	imageapi "github.com/openshift/origin/pkg/image/api"
17 18
 )
... ...
@@ -22,6 +23,12 @@ func (okc *okBuildUpdater) Update(namespace string, build *buildapi.Build) error
22 22
 	return nil
23 23
 }
24 24
 
25
+type okBuildLister struct{}
26
+
27
+func (okc *okBuildLister) List(namespace string, opts kapi.ListOptions) (*buildapi.BuildList, error) {
28
+	return &buildapi.BuildList{Items: []buildapi.Build{}}, nil
29
+}
30
+
25 31
 type errBuildUpdater struct{}
26 32
 
27 33
 func (ec *errBuildUpdater) Update(namespace string, build *buildapi.Build) error {
... ...
@@ -115,6 +122,9 @@ func mockBuild(phase buildapi.BuildPhase, output buildapi.BuildOutput) *buildapi
115 115
 			Namespace: "namespace",
116 116
 			Labels: map[string]string{
117 117
 				"name": "dataBuild",
118
+				// TODO: Switch this test to use Serial policy
119
+				buildapi.BuildRunPolicyLabel: string(buildapi.BuildRunPolicyParallel),
120
+				buildapi.BuildConfigLabel:    "test-bc",
118 121
 			},
119 122
 		},
120 123
 		Spec: buildapi.BuildSpec{
... ...
@@ -138,10 +148,12 @@ func mockBuild(phase buildapi.BuildPhase, output buildapi.BuildOutput) *buildapi
138 138
 func mockBuildController() *BuildController {
139 139
 	return &BuildController{
140 140
 		BuildUpdater:      &okBuildUpdater{},
141
+		BuildLister:       &okBuildLister{},
141 142
 		PodManager:        &okPodManager{},
142 143
 		BuildStrategy:     &okStrategy{},
143 144
 		ImageStreamClient: &okImageStreamClient{},
144 145
 		Recorder:          &record.FakeRecorder{},
146
+		RunPolicies:       policy.GetAllRunPolicies(&okBuildLister{}, &okBuildUpdater{}),
145 147
 	}
146 148
 }
147 149
 
... ...
@@ -405,6 +417,9 @@ func TestHandleBuild(t *testing.T) {
405 405
 
406 406
 		if len(tc.outputSpec) != 0 {
407 407
 			build := ctrl.BuildStrategy.(*okStrategy).build
408
+			if build == nil {
409
+				t.Errorf("(%d) unable to cast build", i)
410
+			}
408 411
 
409 412
 			if build.Spec.Output.To.Name != tc.outputSpec {
410 413
 				t.Errorf("(%d) expected build sent to strategy to have docker spec %s, got %s", i, tc.outputSpec, build.Spec.Output.To.Name)
... ...
@@ -21,6 +21,7 @@ import (
21 21
 	buildapi "github.com/openshift/origin/pkg/build/api"
22 22
 	buildclient "github.com/openshift/origin/pkg/build/client"
23 23
 	buildcontroller "github.com/openshift/origin/pkg/build/controller"
24
+	"github.com/openshift/origin/pkg/build/controller/policy"
24 25
 	strategy "github.com/openshift/origin/pkg/build/controller/strategy"
25 26
 	buildutil "github.com/openshift/origin/pkg/build/util"
26 27
 	osclient "github.com/openshift/origin/pkg/client"
... ...
@@ -63,6 +64,7 @@ type BuildControllerFactory struct {
63 63
 	OSClient            osclient.Interface
64 64
 	KubeClient          kclient.Interface
65 65
 	BuildUpdater        buildclient.BuildUpdater
66
+	BuildLister         buildclient.BuildLister
66 67
 	DockerBuildStrategy *strategy.DockerBuildStrategy
67 68
 	SourceBuildStrategy *strategy.SourceBuildStrategy
68 69
 	CustomBuildStrategy *strategy.CustomBuildStrategy
... ...
@@ -81,8 +83,10 @@ func (factory *BuildControllerFactory) Create() controller.RunnableController {
81 81
 	client := ControllerClient{factory.KubeClient, factory.OSClient}
82 82
 	buildController := &buildcontroller.BuildController{
83 83
 		BuildUpdater:      factory.BuildUpdater,
84
+		BuildLister:       factory.BuildLister,
84 85
 		ImageStreamClient: client,
85 86
 		PodManager:        client,
87
+		RunPolicies:       policy.GetAllRunPolicies(factory.BuildLister, factory.BuildUpdater),
86 88
 		BuildStrategy: &typeBasedFactoryStrategy{
87 89
 			DockerBuildStrategy: factory.DockerBuildStrategy,
88 90
 			SourceBuildStrategy: factory.SourceBuildStrategy,
89 91
new file mode 100644
... ...
@@ -0,0 +1,35 @@
0
+package policy
1
+
2
+import (
3
+	"fmt"
4
+
5
+	buildapi "github.com/openshift/origin/pkg/build/api"
6
+)
7
+
8
+// NoBuildConfigLabelError represents an error caused by the build not having
9
+// the required build config label.
10
+type NoBuildConfigLabelError struct {
11
+	build *buildapi.Build
12
+}
13
+
14
+func NewNoBuildConfigLabelError(build *buildapi.Build) error {
15
+	return NoBuildConfigLabelError{build: build}
16
+}
17
+
18
+func (e NoBuildConfigLabelError) Error() string {
19
+	return fmt.Sprintf("build %s/%s does not have required %q label set", e.build.Namespace, e.build.Name, buildapi.BuildConfigLabel)
20
+}
21
+
22
+// NoBuildNumberLabelError represents an error caused by the build not having
23
+// the required build number annotation.
24
+type NoBuildNumberAnnotationError struct {
25
+	build *buildapi.Build
26
+}
27
+
28
+func NewNoBuildNumberAnnotationError(build *buildapi.Build) error {
29
+	return NoBuildNumberAnnotationError{build: build}
30
+}
31
+
32
+func (e NoBuildNumberAnnotationError) Error() string {
33
+	return fmt.Sprintf("build %s/%s does not have required %q annotation set", e.build.Namespace, e.build.Name, buildapi.BuildNumberAnnotation)
34
+}
0 35
new file mode 100644
... ...
@@ -0,0 +1,32 @@
0
+package policy
1
+
2
+import (
3
+	buildapi "github.com/openshift/origin/pkg/build/api"
4
+	buildclient "github.com/openshift/origin/pkg/build/client"
5
+	buildutil "github.com/openshift/origin/pkg/build/util"
6
+)
7
+
8
+// ParallelPolicy implements the RunPolicy interface. Build created using this
9
+// run policy will always run as soon as they are created.
10
+// This run policy does not guarantee that the builds will complete in same
11
+// order as they were created and using this policy might cause unpredictable
12
+// behavior.
13
+type ParallelPolicy struct {
14
+	BuildLister  buildclient.BuildLister
15
+	BuildUpdater buildclient.BuildUpdater
16
+}
17
+
18
+// IsRunnable implements the RunPolicy interface. The parallel builds are run as soon
19
+// as they are created. There is no build queue as all build run asynchronously.
20
+func (s *ParallelPolicy) IsRunnable(build *buildapi.Build) (bool, error) {
21
+	bcName := buildutil.ConfigNameForBuild(build)
22
+	if len(bcName) == 0 {
23
+		return false, NewNoBuildConfigLabelError(build)
24
+	}
25
+	return !hasRunningSerialBuild(s.BuildLister, build.Namespace, bcName), nil
26
+}
27
+
28
+// OnComplete implements the RunPolicy interface.
29
+func (s *ParallelPolicy) OnComplete(build *buildapi.Build) error {
30
+	return handleComplete(s.BuildLister, s.BuildUpdater, build)
31
+}
0 32
new file mode 100644
... ...
@@ -0,0 +1,64 @@
0
+package policy
1
+
2
+import (
3
+	"testing"
4
+
5
+	buildapi "github.com/openshift/origin/pkg/build/api"
6
+)
7
+
8
+func TestParallelIsRunnableNewBuilds(t *testing.T) {
9
+	allNewBuilds := []buildapi.Build{
10
+		addBuild("build-1", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicyParallel),
11
+		addBuild("build-2", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicyParallel),
12
+		addBuild("build-3", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicyParallel),
13
+	}
14
+	client := newTestClient(allNewBuilds)
15
+	policy := ParallelPolicy{BuildLister: client, BuildUpdater: client}
16
+	for _, build := range allNewBuilds {
17
+		runnable, err := policy.IsRunnable(&build)
18
+		if err != nil {
19
+			t.Errorf("expected no error, got %v", err)
20
+		}
21
+		if !runnable {
22
+			t.Errorf("expected build %s runnable, is not", build.Name)
23
+		}
24
+	}
25
+}
26
+
27
+func TestParallelIsRunnableMixedBuilds(t *testing.T) {
28
+	mixedBuilds := []buildapi.Build{
29
+		addBuild("build-4", "sample-bc", buildapi.BuildPhaseRunning, buildapi.BuildRunPolicyParallel),
30
+		addBuild("build-6", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicyParallel),
31
+		addBuild("build-5", "sample-bc", buildapi.BuildPhasePending, buildapi.BuildRunPolicyParallel),
32
+	}
33
+	client := newTestClient(mixedBuilds)
34
+	policy := ParallelPolicy{BuildLister: client, BuildUpdater: client}
35
+	for _, build := range mixedBuilds {
36
+		runnable, err := policy.IsRunnable(&build)
37
+		if err != nil {
38
+			t.Errorf("expected no error, got %v", err)
39
+		}
40
+		if !runnable {
41
+			t.Errorf("expected build %s runnable, is not", build.Name)
42
+		}
43
+	}
44
+}
45
+
46
+func TestParallelIsRunnableWithSerialRunning(t *testing.T) {
47
+	mixedBuilds := []buildapi.Build{
48
+		addBuild("build-7", "sample-bc", buildapi.BuildPhaseRunning, buildapi.BuildRunPolicySerial),
49
+		addBuild("build-8", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicyParallel),
50
+		addBuild("build-9", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicyParallel),
51
+	}
52
+	client := newTestClient(mixedBuilds)
53
+	policy := ParallelPolicy{BuildLister: client, BuildUpdater: client}
54
+	for _, build := range mixedBuilds {
55
+		runnable, err := policy.IsRunnable(&build)
56
+		if err != nil {
57
+			t.Errorf("expected no error, got %v", err)
58
+		}
59
+		if runnable {
60
+			t.Errorf("expected build %s as not runnable", build.Name)
61
+		}
62
+	}
63
+}
0 64
new file mode 100644
... ...
@@ -0,0 +1,140 @@
0
+package policy
1
+
2
+import (
3
+	"fmt"
4
+	"time"
5
+
6
+	"github.com/golang/glog"
7
+	buildapi "github.com/openshift/origin/pkg/build/api"
8
+	buildclient "github.com/openshift/origin/pkg/build/client"
9
+	buildutil "github.com/openshift/origin/pkg/build/util"
10
+	"k8s.io/kubernetes/pkg/api/errors"
11
+	"k8s.io/kubernetes/pkg/api/unversioned"
12
+	"k8s.io/kubernetes/pkg/util/wait"
13
+)
14
+
15
+// RunPolicy is an interface that define handler for the build runPolicy field.
16
+// The run policy controls how and when the new builds are 'run'.
17
+type RunPolicy interface {
18
+	// IsRunnable returns true of the given build should be executed.
19
+	IsRunnable(*buildapi.Build) (bool, error)
20
+
21
+	// OnComplete allows policy to execute action when the given build just
22
+	// completed.
23
+	OnComplete(*buildapi.Build) error
24
+}
25
+
26
+// GetAllRunPolicies returns a set of all run policies.
27
+func GetAllRunPolicies(lister buildclient.BuildLister, updater buildclient.BuildUpdater) []RunPolicy {
28
+	return []RunPolicy{
29
+		&ParallelPolicy{BuildLister: lister, BuildUpdater: updater},
30
+		&SerialPolicy{BuildLister: lister, BuildUpdater: updater},
31
+		&SerialLatestOnlyPolicy{BuildLister: lister, BuildUpdater: updater},
32
+	}
33
+}
34
+
35
+// ForBuild picks the appropriate run policy for the given build.
36
+func ForBuild(build *buildapi.Build, policies []RunPolicy) RunPolicy {
37
+	for _, s := range policies {
38
+		switch buildutil.BuildRunPolicy(build) {
39
+		case buildapi.BuildRunPolicyParallel:
40
+			if _, ok := s.(*ParallelPolicy); ok {
41
+				glog.V(5).Infof("Using %T run policy for build %s/%s", s, build.Namespace, build.Name)
42
+				return s
43
+			}
44
+		case buildapi.BuildRunPolicySerial:
45
+			if _, ok := s.(*SerialPolicy); ok {
46
+				glog.V(5).Infof("Using %T run policy for build %s/%s", s, build.Namespace, build.Name)
47
+				return s
48
+			}
49
+		case buildapi.BuildRunPolicySerialLatestOnly:
50
+			if _, ok := s.(*SerialLatestOnlyPolicy); ok {
51
+				glog.V(5).Infof("Using %T run policy for build %s/%s", s, build.Namespace, build.Name)
52
+				return s
53
+			}
54
+		}
55
+	}
56
+	return nil
57
+}
58
+
59
+// hasRunningSerialBuild indicates that there is a running or pending serial
60
+// build. This function is used to prevent running parallel builds because
61
+// serial builds should always run alone.
62
+func hasRunningSerialBuild(lister buildclient.BuildLister, namespace, buildConfigName string) bool {
63
+	var hasRunningBuilds bool
64
+	buildutil.BuildConfigBuilds(lister, namespace, buildConfigName, func(b buildapi.Build) bool {
65
+		switch b.Status.Phase {
66
+		case buildapi.BuildPhasePending, buildapi.BuildPhaseRunning:
67
+			switch buildutil.BuildRunPolicy(&b) {
68
+			case buildapi.BuildRunPolicySerial, buildapi.BuildRunPolicySerialLatestOnly:
69
+				hasRunningBuilds = true
70
+			}
71
+		}
72
+		return false
73
+	})
74
+	return hasRunningBuilds
75
+}
76
+
77
+// GetNextConfigBuild returns the build that will be executed next for the given
78
+// build configuration. It also returns the indication whether there are
79
+// currently running builds, to make sure there is no race-condition between
80
+// re-listing the builds.
81
+func GetNextConfigBuild(lister buildclient.BuildLister, namespace, buildConfigName string) (*buildapi.Build, bool, error) {
82
+	var (
83
+		nextBuild           *buildapi.Build
84
+		hasRunningBuilds    bool
85
+		previousBuildNumber int64
86
+	)
87
+	builds, err := buildutil.BuildConfigBuilds(lister, namespace, buildConfigName, func(b buildapi.Build) bool {
88
+		switch b.Status.Phase {
89
+		case buildapi.BuildPhasePending, buildapi.BuildPhaseRunning:
90
+			hasRunningBuilds = true
91
+			return false
92
+		}
93
+		// Only 'new' build can be scheduled to run next
94
+		return b.Status.Phase == buildapi.BuildPhaseNew
95
+	})
96
+	if err != nil {
97
+		return nil, hasRunningBuilds, err
98
+	}
99
+
100
+	for i, b := range builds.Items {
101
+		buildNumber, err := buildutil.BuildNumber(&b)
102
+		if err != nil {
103
+			return nil, hasRunningBuilds, err
104
+		}
105
+		if previousBuildNumber == 0 || buildNumber < previousBuildNumber {
106
+			nextBuild = &builds.Items[i]
107
+			previousBuildNumber = buildNumber
108
+		}
109
+	}
110
+	return nextBuild, hasRunningBuilds, nil
111
+}
112
+
113
+// handleComplete represents the default OnComplete handler. This Handler will
114
+// check which build should be run next and update the StartTimestamp field for
115
+// that build. That will trigger HandleBuild() to process that build immediately
116
+// and as a result the build is immediately executed.
117
+func handleComplete(lister buildclient.BuildLister, updater buildclient.BuildUpdater, build *buildapi.Build) error {
118
+	bcName := buildutil.ConfigNameForBuild(build)
119
+	if len(bcName) == 0 {
120
+		return NewNoBuildConfigLabelError(build)
121
+	}
122
+	nextBuild, hasRunningBuilds, err := GetNextConfigBuild(lister, build.Namespace, bcName)
123
+	if err != nil {
124
+		return fmt.Errorf("unable to get the next build for %s/%s: %v", build.Namespace, build.Name, err)
125
+	}
126
+	if hasRunningBuilds || nextBuild == nil {
127
+		return nil
128
+	}
129
+	now := unversioned.Now()
130
+	nextBuild.Status.StartTimestamp = &now
131
+	return wait.Poll(500*time.Millisecond, 5*time.Second, func() (bool, error) {
132
+		err := updater.Update(nextBuild.Namespace, nextBuild)
133
+		if err != nil && errors.IsConflict(err) {
134
+			glog.V(5).Infof("Error updating build %s/%s: %v (will retry)", nextBuild.Namespace, nextBuild.Name, err)
135
+			return false, nil
136
+		}
137
+		return true, err
138
+	})
139
+}
0 140
new file mode 100644
... ...
@@ -0,0 +1,122 @@
0
+package policy
1
+
2
+import (
3
+	"strings"
4
+	"testing"
5
+
6
+	"errors"
7
+
8
+	buildapi "github.com/openshift/origin/pkg/build/api"
9
+	kapi "k8s.io/kubernetes/pkg/api"
10
+	kerrors "k8s.io/kubernetes/pkg/api/errors"
11
+)
12
+
13
+type fakeBuildClient struct {
14
+	builds         *buildapi.BuildList
15
+	updateErrCount int
16
+}
17
+
18
+func newTestClient(builds []buildapi.Build) *fakeBuildClient {
19
+	return &fakeBuildClient{builds: &buildapi.BuildList{Items: builds}}
20
+}
21
+
22
+func (f *fakeBuildClient) List(namespace string, opts kapi.ListOptions) (*buildapi.BuildList, error) {
23
+	return f.builds, nil
24
+}
25
+
26
+func (f *fakeBuildClient) Update(namespace string, build *buildapi.Build) error {
27
+	// Make sure every update fails at least once with conflict to ensure build updates are
28
+	// retried.
29
+	if f.updateErrCount == 0 {
30
+		f.updateErrCount = 1
31
+		return kerrors.NewConflict(kapi.Resource("builds"), build.Name, errors.New("confict"))
32
+	} else {
33
+		f.updateErrCount = 0
34
+	}
35
+	for i, item := range f.builds.Items {
36
+		if build.Name == item.Name {
37
+			f.builds.Items[i] = *build
38
+		}
39
+	}
40
+	return nil
41
+}
42
+
43
+func addBuild(name, bcName string, phase buildapi.BuildPhase, policy buildapi.BuildRunPolicy) buildapi.Build {
44
+	parts := strings.Split(name, "-")
45
+	return buildapi.Build{
46
+		Spec: buildapi.BuildSpec{},
47
+		ObjectMeta: kapi.ObjectMeta{
48
+			Name:      name,
49
+			Namespace: "test",
50
+			Labels: map[string]string{
51
+				buildapi.BuildRunPolicyLabel: string(policy),
52
+				buildapi.BuildConfigLabel:    bcName,
53
+			},
54
+			Annotations: map[string]string{
55
+				buildapi.BuildNumberAnnotation: parts[len(parts)-1],
56
+			},
57
+		},
58
+		Status: buildapi.BuildStatus{Phase: phase},
59
+	}
60
+}
61
+
62
+func TestForBuild(t *testing.T) {
63
+	builds := []buildapi.Build{
64
+		addBuild("build-1", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicyParallel),
65
+		addBuild("build-2", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicySerial),
66
+		addBuild("build-3", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicySerialLatestOnly),
67
+	}
68
+	client := newTestClient(builds)
69
+	policies := GetAllRunPolicies(client, client)
70
+
71
+	if policy := ForBuild(&builds[0], policies); policy != nil {
72
+		if _, ok := policy.(*ParallelPolicy); !ok {
73
+			t.Errorf("expected Parallel policy for build-1, got %T", policy)
74
+		}
75
+	} else {
76
+		t.Errorf("expected Parallel policy for build-1, got nil")
77
+	}
78
+
79
+	if policy := ForBuild(&builds[1], policies); policy != nil {
80
+		if _, ok := policy.(*SerialPolicy); !ok {
81
+			t.Errorf("expected Serial policy for build-2, got %T", policy)
82
+		}
83
+	} else {
84
+		t.Errorf("expected Serial policy for build-2, got nil")
85
+	}
86
+
87
+	if policy := ForBuild(&builds[2], policies); policy != nil {
88
+		if _, ok := policy.(*SerialLatestOnlyPolicy); !ok {
89
+			t.Errorf("expected SerialLatestOnly policy for build-3, got %T", policy)
90
+		}
91
+	} else {
92
+		t.Errorf("expected SerialLatestOnly policy for build-3, got nil")
93
+	}
94
+}
95
+
96
+func TestHandleComplete(t *testing.T) {
97
+	builds := []buildapi.Build{
98
+		addBuild("build-1", "sample-bc", buildapi.BuildPhaseComplete, buildapi.BuildRunPolicySerial),
99
+		addBuild("build-2", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicySerial),
100
+		addBuild("build-3", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicySerial),
101
+	}
102
+
103
+	client := newTestClient(builds)
104
+
105
+	if err := handleComplete(client, client, &builds[0]); err != nil {
106
+		t.Errorf("unexpected error %v", err)
107
+	}
108
+
109
+	resultBuilds, err := client.List("test", kapi.ListOptions{})
110
+	if err != nil {
111
+		t.Errorf("unexpected error: %v", err)
112
+	}
113
+
114
+	if resultBuilds.Items[1].Status.StartTimestamp == nil {
115
+		t.Errorf("build-2 should have Status.StartTimestamp set to trigger it")
116
+	}
117
+
118
+	if resultBuilds.Items[2].Status.StartTimestamp != nil {
119
+		t.Errorf("build-3 should not have Status.StartTimestamp set")
120
+	}
121
+}
0 122
new file mode 100644
... ...
@@ -0,0 +1,32 @@
0
+package policy
1
+
2
+import (
3
+	buildapi "github.com/openshift/origin/pkg/build/api"
4
+	buildclient "github.com/openshift/origin/pkg/build/client"
5
+	buildutil "github.com/openshift/origin/pkg/build/util"
6
+)
7
+
8
+// SerialPolicy implements the RunPolicy interface. Using this run policy, every
9
+// created build is put into a queue. The serial run policy guarantees that
10
+// all builds are executed synchroniously in the same order as they were
11
+// created. This will produce consistent results, but block the build execution until the
12
+// previous builds are complete.
13
+type SerialPolicy struct {
14
+	BuildLister  buildclient.BuildLister
15
+	BuildUpdater buildclient.BuildUpdater
16
+}
17
+
18
+// IsRunnable implements the RunPolicy interface.
19
+func (s *SerialPolicy) IsRunnable(build *buildapi.Build) (bool, error) {
20
+	bcName := buildutil.ConfigNameForBuild(build)
21
+	if len(bcName) == 0 {
22
+		return false, NewNoBuildConfigLabelError(build)
23
+	}
24
+	nextBuild, runningBuilds, err := GetNextConfigBuild(s.BuildLister, build.Namespace, bcName)
25
+	return !runningBuilds && (nextBuild != nil && nextBuild.Name == build.Name), err
26
+}
27
+
28
+// OnComplete implements the RunPolicy interface.
29
+func (s *SerialPolicy) OnComplete(build *buildapi.Build) error {
30
+	return handleComplete(s.BuildLister, s.BuildUpdater, build)
31
+}
0 32
new file mode 100644
... ...
@@ -0,0 +1,95 @@
0
+package policy
1
+
2
+import (
3
+	"time"
4
+
5
+	"k8s.io/kubernetes/pkg/api/errors"
6
+
7
+	kerrors "k8s.io/kubernetes/pkg/util/errors"
8
+	"k8s.io/kubernetes/pkg/util/wait"
9
+
10
+	"github.com/golang/glog"
11
+	buildapi "github.com/openshift/origin/pkg/build/api"
12
+	buildclient "github.com/openshift/origin/pkg/build/client"
13
+	buildutil "github.com/openshift/origin/pkg/build/util"
14
+)
15
+
16
+// SerialLatestOnlyPolicy implements the RunPolicy interface. This variant of
17
+// the serial build policy makes sure that builds are executed in same order as
18
+// they were created, but when a new build is created, the previous, queued
19
+// build is cancelled, always making the latest created build run as next. This
20
+// will produce consistent results, but might not suit the CI/CD flow where user
21
+// expect that every commit is built.
22
+type SerialLatestOnlyPolicy struct {
23
+	BuildUpdater buildclient.BuildUpdater
24
+	BuildLister  buildclient.BuildLister
25
+}
26
+
27
+// IsRunnable implements the RunPolicy interface.
28
+// Calling this function on a build mean that any previous build that is in
29
+// 'new' phase will be automatically cancelled. This will also cancel any
30
+// "serial" build (when you changed the build config run policy on-the-fly).
31
+func (s *SerialLatestOnlyPolicy) IsRunnable(build *buildapi.Build) (bool, error) {
32
+	bcName := buildutil.ConfigNameForBuild(build)
33
+	if len(bcName) == 0 {
34
+		return false, NewNoBuildConfigLabelError(build)
35
+	}
36
+	if err := kerrors.NewAggregate(s.cancelPreviousBuilds(build)); err != nil {
37
+		return false, err
38
+	}
39
+	nextBuild, runningBuilds, err := GetNextConfigBuild(s.BuildLister, build.Namespace, bcName)
40
+	if err != nil || runningBuilds {
41
+		return false, err
42
+	}
43
+	return nextBuild != nil && nextBuild.Name == build.Name, err
44
+}
45
+
46
+// IsRunnable implements the Scheduler interface.
47
+func (s *SerialLatestOnlyPolicy) OnComplete(build *buildapi.Build) error {
48
+	return handleComplete(s.BuildLister, s.BuildUpdater, build)
49
+}
50
+
51
+// cancelPreviousBuilds cancels all queued builds that have the build sequence number
52
+// lower than the given build. It retries the cancellation in case of conflict.
53
+func (s *SerialLatestOnlyPolicy) cancelPreviousBuilds(build *buildapi.Build) []error {
54
+	bcName := buildutil.ConfigNameForBuild(build)
55
+	if len(bcName) == 0 {
56
+		return []error{NewNoBuildConfigLabelError(build)}
57
+	}
58
+	currentBuildNumber, err := buildutil.BuildNumber(build)
59
+	if err != nil {
60
+		return []error{NewNoBuildNumberAnnotationError(build)}
61
+	}
62
+	builds, err := buildutil.BuildConfigBuilds(s.BuildLister, build.Namespace, bcName, func(b buildapi.Build) bool {
63
+		// Do not cancel the complete builds, builds that were already cancelled, or
64
+		// running builds.
65
+		if buildutil.IsBuildComplete(&b) || b.Status.Phase == buildapi.BuildPhaseRunning {
66
+			return false
67
+		}
68
+
69
+		// Prevent race-condition when there is a newer build than this and we don't
70
+		// want to cancel it. The HandleBuild() function that runs for that build
71
+		// will cancel this build.
72
+		buildNumber, _ := buildutil.BuildNumber(&b)
73
+		return buildNumber < currentBuildNumber
74
+	})
75
+	if err != nil {
76
+		return []error{err}
77
+	}
78
+	var result = []error{}
79
+	for _, b := range builds.Items {
80
+		err := wait.Poll(500*time.Millisecond, 5*time.Second, func() (bool, error) {
81
+			b.Status.Cancelled = true
82
+			err := s.BuildUpdater.Update(b.Namespace, &b)
83
+			if err != nil && errors.IsConflict(err) {
84
+				glog.V(5).Infof("Error cancelling build %s/%s: %v (will retry)", b.Namespace, b.Name, err)
85
+				return false, nil
86
+			}
87
+			return true, err
88
+		})
89
+		if err != nil {
90
+			result = append(result, err)
91
+		}
92
+	}
93
+	return result
94
+}
0 95
new file mode 100644
... ...
@@ -0,0 +1,144 @@
0
+package policy
1
+
2
+import (
3
+	"testing"
4
+
5
+	buildapi "github.com/openshift/origin/pkg/build/api"
6
+	kapi "k8s.io/kubernetes/pkg/api"
7
+)
8
+
9
+func TestSerialLatestOnlyIsRunnableNewBuilds(t *testing.T) {
10
+	allNewBuilds := []buildapi.Build{
11
+		addBuild("build-1", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicySerialLatestOnly),
12
+		addBuild("build-2", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicySerialLatestOnly),
13
+		addBuild("build-3", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicySerialLatestOnly),
14
+	}
15
+	client := newTestClient(allNewBuilds)
16
+	policy := SerialLatestOnlyPolicy{BuildLister: client, BuildUpdater: client}
17
+	runnableBuilds := []string{
18
+		"build-1",
19
+	}
20
+	shouldRun := func(name string) bool {
21
+		for _, b := range runnableBuilds {
22
+			if b == name {
23
+				return true
24
+			}
25
+		}
26
+		return false
27
+	}
28
+	shouldNotRun := func(name string) bool {
29
+		return !shouldRun(name)
30
+	}
31
+	for _, build := range allNewBuilds {
32
+		runnable, err := policy.IsRunnable(&build)
33
+		if err != nil {
34
+			t.Errorf("expected no error, got %v", err)
35
+		}
36
+		if runnable && shouldNotRun(build.Name) {
37
+			t.Errorf("%s should not be runnable", build.Name)
38
+		}
39
+		if !runnable && shouldRun(build.Name) {
40
+			t.Errorf("%s should be runnable, it is not", build.Name)
41
+		}
42
+	}
43
+	builds, err := client.List("test", kapi.ListOptions{})
44
+	if err != nil {
45
+		t.Errorf("unexpected error: %v", err)
46
+	}
47
+	if !builds.Items[1].Status.Cancelled {
48
+		t.Errorf("expected build-2 to be cancelled")
49
+	}
50
+}
51
+
52
+func TestSerialLatestOnlyIsRunnableMixed(t *testing.T) {
53
+	allNewBuilds := []buildapi.Build{
54
+		addBuild("build-1", "sample-bc", buildapi.BuildPhaseComplete, buildapi.BuildRunPolicySerialLatestOnly),
55
+		addBuild("build-2", "sample-bc", buildapi.BuildPhaseCancelled, buildapi.BuildRunPolicySerialLatestOnly),
56
+		addBuild("build-3", "sample-bc", buildapi.BuildPhaseRunning, buildapi.BuildRunPolicySerialLatestOnly),
57
+		addBuild("build-4", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicySerialLatestOnly),
58
+	}
59
+	client := newTestClient(allNewBuilds)
60
+	policy := SerialLatestOnlyPolicy{BuildLister: client, BuildUpdater: client}
61
+	for _, build := range allNewBuilds {
62
+		runnable, err := policy.IsRunnable(&build)
63
+		if err != nil {
64
+			t.Errorf("expected no error, got %v", err)
65
+		}
66
+		if runnable {
67
+			t.Errorf("%s should not be runnable", build.Name)
68
+		}
69
+	}
70
+	builds, err := client.List("test", kapi.ListOptions{})
71
+	if err != nil {
72
+		t.Errorf("unexpected error: %v", err)
73
+	}
74
+	if builds.Items[0].Status.Cancelled {
75
+		t.Errorf("expected build-1 is complete and should not be cancelled")
76
+	}
77
+	if builds.Items[2].Status.Cancelled {
78
+		t.Errorf("expected build-3 is running and should not be cancelled")
79
+	}
80
+	if builds.Items[3].Status.Cancelled {
81
+		t.Errorf("expected build-4 will run next and should not be cancelled")
82
+	}
83
+}
84
+
85
+func TestSerialLatestOnlyIsRunnableBuildsWithErrors(t *testing.T) {
86
+	builds := []buildapi.Build{
87
+		addBuild("build-1", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicySerialLatestOnly),
88
+		addBuild("build-2", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicySerialLatestOnly),
89
+		addBuild("build-3", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicySerialLatestOnly),
90
+	}
91
+
92
+	// The build-1 will lack required labels
93
+	builds[0].ObjectMeta.Labels = map[string]string{}
94
+
95
+	// The build-2 will lack the build config label
96
+	builds[1].ObjectMeta.Labels = map[string]string{
97
+		buildapi.BuildRunPolicyLabel: "SerialLatestOnly",
98
+	}
99
+
100
+	// The build-3 will lack the build number annotation
101
+	builds[2].ObjectMeta.Annotations = map[string]string{}
102
+
103
+	client := newTestClient(builds)
104
+	policy := SerialLatestOnlyPolicy{BuildLister: client, BuildUpdater: client}
105
+
106
+	if _, err := policy.IsRunnable(&builds[0]); err != nil {
107
+		if _, ok := err.(NoBuildConfigLabelError); !ok {
108
+			t.Errorf("expected NoBuildConfigLabelError, got %T", err)
109
+		}
110
+	} else {
111
+		t.Errorf("expected error for build-1")
112
+	}
113
+	if _, err := policy.IsRunnable(&builds[1]); err != nil {
114
+		if _, ok := err.(NoBuildConfigLabelError); !ok {
115
+			t.Errorf("expected NoBuildConfigLabelError, got %T", err)
116
+		}
117
+	} else {
118
+		t.Errorf("expected error for build-2")
119
+	}
120
+	// No type-check as this error is returned as kerrors.aggregate
121
+	if _, err := policy.IsRunnable(&builds[2]); err == nil {
122
+		t.Errorf("expected error for build-3")
123
+	}
124
+
125
+	if err := policy.OnComplete(&builds[0]); err != nil {
126
+		if _, ok := err.(NoBuildConfigLabelError); !ok {
127
+			t.Errorf("expected NoBuildConfigLabelError, got %T", err)
128
+		}
129
+	} else {
130
+		t.Errorf("expected error for build-1")
131
+	}
132
+	if err := policy.OnComplete(&builds[1]); err != nil {
133
+		if _, ok := err.(NoBuildConfigLabelError); !ok {
134
+			t.Errorf("expected NoBuildConfigLabelError, got %T", err)
135
+		}
136
+	} else {
137
+		t.Errorf("expected error for build-2")
138
+	}
139
+	// No type-check as this error is returned as kerrors.aggregate
140
+	if err := policy.OnComplete(&builds[2]); err == nil {
141
+		t.Errorf("expected error for build-3")
142
+	}
143
+}
0 144
new file mode 100644
... ...
@@ -0,0 +1,43 @@
0
+package policy
1
+
2
+import (
3
+	"testing"
4
+
5
+	buildapi "github.com/openshift/origin/pkg/build/api"
6
+)
7
+
8
+func TestSerialIsRunnableNewBuilds(t *testing.T) {
9
+	allNewBuilds := []buildapi.Build{
10
+		addBuild("build-1", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicySerial),
11
+		addBuild("build-2", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicySerial),
12
+		addBuild("build-3", "sample-bc", buildapi.BuildPhaseNew, buildapi.BuildRunPolicySerial),
13
+	}
14
+	client := newTestClient(allNewBuilds)
15
+	policy := SerialPolicy{BuildLister: client, BuildUpdater: client}
16
+	runnableBuilds := []string{
17
+		"build-1",
18
+	}
19
+	shouldRun := func(name string) bool {
20
+		for _, b := range runnableBuilds {
21
+			if b == name {
22
+				return true
23
+			}
24
+		}
25
+		return false
26
+	}
27
+	shouldNotRun := func(name string) bool {
28
+		return !shouldRun(name)
29
+	}
30
+	for _, build := range allNewBuilds {
31
+		runnable, err := policy.IsRunnable(&build)
32
+		if err != nil {
33
+			t.Errorf("expected no error, got %v", err)
34
+		}
35
+		if runnable && shouldNotRun(build.Name) {
36
+			t.Errorf("%s should not be runnable", build.Name)
37
+		}
38
+		if !runnable && shouldRun(build.Name) {
39
+			t.Errorf("%s should be runnable, it is not", build.Name)
40
+		}
41
+	}
42
+}
... ...
@@ -421,6 +421,7 @@ func (g *BuildGenerator) generateBuildFromConfig(ctx kapi.Context, bc *buildapi.
421 421
 	}
422 422
 	build.Labels[buildapi.BuildConfigLabelDeprecated] = bcCopy.Name
423 423
 	build.Labels[buildapi.BuildConfigLabel] = bcCopy.Name
424
+	build.Labels[buildapi.BuildRunPolicyLabel] = string(bc.Spec.RunPolicy)
424 425
 
425 426
 	builderSecrets, err := g.FetchServiceAccountSecrets(bc.Namespace, serviceAccount)
426 427
 	if err != nil {
... ...
@@ -29,6 +29,7 @@ func validBuildConfig() *api.BuildConfig {
29 29
 	return &api.BuildConfig{
30 30
 		ObjectMeta: kapi.ObjectMeta{Name: "configid"},
31 31
 		Spec: api.BuildConfigSpec{
32
+			RunPolicy: api.BuildRunPolicySerial,
32 33
 			BuildSpec: api.BuildSpec{
33 34
 				Source: api.BuildSource{
34 35
 					Git: &api.GitBuildSource{
... ...
@@ -19,6 +19,7 @@ func TestBuildConfigStrategy(t *testing.T) {
19 19
 	buildConfig := &buildapi.BuildConfig{
20 20
 		ObjectMeta: kapi.ObjectMeta{Name: "config-id", Namespace: "namespace"},
21 21
 		Spec: buildapi.BuildConfigSpec{
22
+			RunPolicy: buildapi.BuildRunPolicySerial,
22 23
 			Triggers: []buildapi.BuildTriggerPolicy{
23 24
 				{
24 25
 					GitHubWebHook: &buildapi.WebHookTrigger{Secret: "12345"},
... ...
@@ -8,7 +8,9 @@ import (
8 8
 	kapi "k8s.io/kubernetes/pkg/api"
9 9
 	"k8s.io/kubernetes/pkg/labels"
10 10
 
11
+	"github.com/golang/glog"
11 12
 	buildapi "github.com/openshift/origin/pkg/build/api"
13
+	buildclient "github.com/openshift/origin/pkg/build/client"
12 14
 )
13 15
 
14 16
 const (
... ...
@@ -72,6 +74,33 @@ func IsPaused(bc *buildapi.BuildConfig) bool {
72 72
 	return strings.ToLower(bc.Annotations[buildapi.BuildConfigPausedAnnotation]) == "true"
73 73
 }
74 74
 
75
+// BuildNumber returns the given build number.
76
+func BuildNumber(build *buildapi.Build) (int64, error) {
77
+	annotations := build.GetAnnotations()
78
+	if stringNumber, ok := annotations[buildapi.BuildNumberAnnotation]; ok {
79
+		return strconv.ParseInt(stringNumber, 10, 64)
80
+	}
81
+	return 0, fmt.Errorf("build %s/%s does not have %s annotation", build.Namespace, build.Name, buildapi.BuildNumberAnnotation)
82
+}
83
+
84
+// BuildRunPolicy returns the scheduling policy for the build based on the
85
+// "queued" label.
86
+func BuildRunPolicy(build *buildapi.Build) buildapi.BuildRunPolicy {
87
+	labels := build.GetLabels()
88
+	if value, found := labels[buildapi.BuildRunPolicyLabel]; found {
89
+		switch value {
90
+		case "Parallel":
91
+			return buildapi.BuildRunPolicyParallel
92
+		case "Serial":
93
+			return buildapi.BuildRunPolicySerial
94
+		case "SerialLatestOnly":
95
+			return buildapi.BuildRunPolicySerialLatestOnly
96
+		}
97
+	}
98
+	glog.V(5).Infof("Build %s/%s does not have start policy label set, using default (Serial)")
99
+	return buildapi.BuildRunPolicySerial
100
+}
101
+
75 102
 // BuildNameForConfigVersion returns the name of the version-th build
76 103
 // for the config that has the provided name.
77 104
 func BuildNameForConfigVersion(name string, version int) string {
... ...
@@ -90,6 +119,30 @@ func BuildConfigSelectorDeprecated(name string) labels.Selector {
90 90
 	return labels.Set{buildapi.BuildConfigLabelDeprecated: name}.AsSelector()
91 91
 }
92 92
 
93
+type buildFilter func(buildapi.Build) bool
94
+
95
+// BuildConfigBuilds return a list of builds for the given build config.
96
+// Optionally you can specify a filter function to select only builds that
97
+// matches your criteria.
98
+func BuildConfigBuilds(c buildclient.BuildLister, namespace, name string, filterFunc buildFilter) (*buildapi.BuildList, error) {
99
+	result, err := c.List(namespace, kapi.ListOptions{
100
+		LabelSelector: BuildConfigSelector(name),
101
+	})
102
+	if err != nil {
103
+		return nil, err
104
+	}
105
+	if filterFunc == nil {
106
+		return result, nil
107
+	}
108
+	filteredList := &buildapi.BuildList{TypeMeta: result.TypeMeta, ListMeta: result.ListMeta}
109
+	for _, b := range result.Items {
110
+		if filterFunc(b) {
111
+			filteredList.Items = append(filteredList.Items, b)
112
+		}
113
+	}
114
+	return filteredList, nil
115
+}
116
+
93 117
 // ConfigNameForBuild returns the name of the build config from a
94 118
 // build name.
95 119
 func ConfigNameForBuild(build *buildapi.Build) string {
... ...
@@ -429,6 +429,7 @@ func (d *BuildConfigDescriber) Describe(namespace, name string) (string, error)
429 429
 			formatString(out, "Latest Version", strconv.Itoa(buildConfig.Status.LastVersion))
430 430
 		}
431 431
 		describeBuildSpec(buildConfig.Spec.BuildSpec, out)
432
+		formatString(out, "\nBuild Run Policy", string(buildConfig.Spec.RunPolicy))
432 433
 		d.DescribeTriggers(buildConfig, out)
433 434
 		if len(buildList.Items) == 0 {
434 435
 			return nil
... ...
@@ -220,9 +220,10 @@ func (c *MasterConfig) RunBuildController() {
220 220
 
221 221
 	osclient, kclient := c.BuildControllerClients()
222 222
 	factory := buildcontrollerfactory.BuildControllerFactory{
223
-		OSClient:     osclient,
224 223
 		KubeClient:   kclient,
224
+		OSClient:     osclient,
225 225
 		BuildUpdater: buildclient.NewOSClientBuildClient(osclient),
226
+		BuildLister:  buildclient.NewOSClientBuildClient(osclient),
226 227
 		DockerBuildStrategy: &buildstrategy.DockerBuildStrategy{
227 228
 			Image: dockerImage,
228 229
 			// TODO: this will be set to --storage-version (the internal schema we use)
229 230
new file mode 100644
... ...
@@ -0,0 +1,257 @@
0
+package builds
1
+
2
+import (
3
+	"fmt"
4
+	"strings"
5
+
6
+	g "github.com/onsi/ginkgo"
7
+	o "github.com/onsi/gomega"
8
+
9
+	buildapi "github.com/openshift/origin/pkg/build/api"
10
+	buildclient "github.com/openshift/origin/pkg/build/client"
11
+	buildutil "github.com/openshift/origin/pkg/build/util"
12
+	exutil "github.com/openshift/origin/test/extended/util"
13
+	kapi "k8s.io/kubernetes/pkg/api"
14
+)
15
+
16
+var _ = g.Describe("[builds][Slow] using build configuration runPolicy", func() {
17
+	defer g.GinkgoRecover()
18
+	var (
19
+		// Use invalid source here as we don't care about the result
20
+		oc = exutil.NewCLI("cli-build-run-policy", 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
+		// Create all fixtures
28
+		oc.Run("create").Args("-f", exutil.FixturePath("..", "extended", "fixtures", "run_policy")).Execute()
29
+	})
30
+
31
+	g.Describe("build configuration with Parallel build run policy", func() {
32
+		g.It("runs the builds in parallel", func() {
33
+			g.By("starting multiple builds")
34
+			var (
35
+				startedBuilds []string
36
+				counter       int
37
+			)
38
+			bcName := "sample-parallel-build"
39
+
40
+			buildWatch, err := oc.REST().Builds(oc.Namespace()).Watch(kapi.ListOptions{
41
+				LabelSelector: buildutil.BuildConfigSelector(bcName),
42
+			})
43
+			defer buildWatch.Stop()
44
+
45
+			// Start first build
46
+			out, err := oc.Run("start-build").Args(bcName).Output()
47
+			o.Expect(err).NotTo(o.HaveOccurred())
48
+			o.Expect(strings.TrimSpace(out)).ShouldNot(o.HaveLen(0))
49
+			startedBuilds = append(startedBuilds, strings.TrimSpace(out))
50
+
51
+			// Wait for it to become running
52
+			for {
53
+				event := <-buildWatch.ResultChan()
54
+				build := event.Object.(*buildapi.Build)
55
+				o.Expect(buildutil.IsBuildComplete(build)).Should(o.BeFalse())
56
+				if build.Name == startedBuilds[0] && build.Status.Phase == buildapi.BuildPhaseRunning {
57
+					break
58
+				}
59
+			}
60
+
61
+			for i := 0; i < 2; i++ {
62
+				out, err := oc.Run("start-build").Args(bcName).Output()
63
+				o.Expect(err).NotTo(o.HaveOccurred())
64
+				o.Expect(strings.TrimSpace(out)).ShouldNot(o.HaveLen(0))
65
+				startedBuilds = append(startedBuilds, strings.TrimSpace(out))
66
+			}
67
+
68
+			o.Expect(err).NotTo(o.HaveOccurred())
69
+
70
+			for {
71
+				event := <-buildWatch.ResultChan()
72
+				build := event.Object.(*buildapi.Build)
73
+				if build.Name == startedBuilds[0] {
74
+					if buildutil.IsBuildComplete(build) {
75
+						break
76
+					}
77
+					continue
78
+				}
79
+				// When the the other two builds we started after waiting for the first
80
+				// build to become running are Pending, verify the first build is still
81
+				// running (so the other two builds are started in parallel with first
82
+				// build).
83
+				// TODO: This might introduce flakes in case the first build complete
84
+				// sooner or fail.
85
+				if build.Status.Phase == buildapi.BuildPhasePending {
86
+					c := buildclient.NewOSClientBuildClient(oc.REST())
87
+					firstBuildRunning := false
88
+					_, err := buildutil.BuildConfigBuilds(c, oc.Namespace(), bcName, func(b buildapi.Build) bool {
89
+						if b.Name == startedBuilds[0] && b.Status.Phase == buildapi.BuildPhaseRunning {
90
+							firstBuildRunning = true
91
+						}
92
+						return false
93
+					})
94
+					o.Expect(err).NotTo(o.HaveOccurred())
95
+					o.Expect(firstBuildRunning).Should(o.BeTrue())
96
+					counter++
97
+				}
98
+				// When the build failed or completed prematurely, fail the test
99
+				o.Expect(buildutil.IsBuildComplete(build)).Should(o.BeFalse())
100
+				if counter == 2 {
101
+					break
102
+				}
103
+			}
104
+			o.Expect(counter).Should(o.BeEquivalentTo(2))
105
+		})
106
+	})
107
+
108
+	g.Describe("build configuration with Serial build run policy", func() {
109
+		g.It("runs the builds in serial order", func() {
110
+			g.By("starting multiple builds")
111
+			var (
112
+				startedBuilds []string
113
+				counter       int
114
+			)
115
+
116
+			bcName := "sample-serial-build"
117
+			buildVerified := map[string]bool{}
118
+
119
+			for i := 0; i < 3; i++ {
120
+				out, err := oc.Run("start-build").Args(bcName).Output()
121
+				o.Expect(err).NotTo(o.HaveOccurred())
122
+				startedBuilds = append(startedBuilds, strings.TrimSpace(out))
123
+			}
124
+
125
+			buildWatch, err := oc.REST().Builds(oc.Namespace()).Watch(kapi.ListOptions{
126
+				LabelSelector: buildutil.BuildConfigSelector(bcName),
127
+			})
128
+			defer buildWatch.Stop()
129
+			o.Expect(err).NotTo(o.HaveOccurred())
130
+
131
+			for {
132
+				event := <-buildWatch.ResultChan()
133
+				build := event.Object.(*buildapi.Build)
134
+				if build.Status.Phase == buildapi.BuildPhaseRunning {
135
+					// Ignore events from complete builds (if there are any) if we already
136
+					// verified the build.
137
+					if _, exists := buildVerified[build.Name]; exists {
138
+						continue
139
+					}
140
+					// Verify there are no other running or pending builds than this
141
+					// build as serial build always runs alone.
142
+					c := buildclient.NewOSClientBuildClient(oc.REST())
143
+					builds, err := buildutil.BuildConfigBuilds(c, oc.Namespace(), bcName, func(b buildapi.Build) bool {
144
+						if b.Name == build.Name {
145
+							return false
146
+						}
147
+						if b.Status.Phase == buildapi.BuildPhaseRunning || b.Status.Phase == buildapi.BuildPhasePending {
148
+							return true
149
+						}
150
+						return false
151
+					})
152
+					o.Expect(err).NotTo(o.HaveOccurred())
153
+					o.Expect(builds.Items).Should(o.BeEmpty())
154
+
155
+					// The builds should start in the same order as they were created.
156
+					o.Expect(build.Name).Should(o.BeEquivalentTo(startedBuilds[counter]))
157
+
158
+					buildVerified[build.Name] = true
159
+					counter++
160
+				}
161
+				if counter == len(startedBuilds) {
162
+					break
163
+				}
164
+			}
165
+		})
166
+	})
167
+
168
+	g.Describe("build configuration with SerialLatestOnly build run policy", func() {
169
+		g.It("runs the builds in serial order but cancel previous builds", func() {
170
+			g.By("starting multiple builds")
171
+			var (
172
+				startedBuilds []string
173
+				counter       int
174
+				wasCancelled  bool
175
+			)
176
+
177
+			bcName := "sample-serial-latest-only-build"
178
+			buildVerified := map[string]bool{}
179
+			buildWatch, err := oc.REST().Builds(oc.Namespace()).Watch(kapi.ListOptions{
180
+				LabelSelector: buildutil.BuildConfigSelector(bcName),
181
+			})
182
+			defer buildWatch.Stop()
183
+			o.Expect(err).NotTo(o.HaveOccurred())
184
+
185
+			out, err := oc.Run("start-build").Args(bcName).Output()
186
+			o.Expect(err).NotTo(o.HaveOccurred())
187
+			startedBuilds = append(startedBuilds, strings.TrimSpace(out))
188
+			// Wait for the first build to become running
189
+			for {
190
+				event := <-buildWatch.ResultChan()
191
+				build := event.Object.(*buildapi.Build)
192
+				if build.Name == startedBuilds[0] {
193
+					if build.Status.Phase == buildapi.BuildPhaseRunning {
194
+						break
195
+					}
196
+					o.Expect(buildutil.IsBuildComplete(build)).Should(o.BeFalse())
197
+				}
198
+			}
199
+			// Trigger another two builds
200
+			for i := 0; i < 2; i++ {
201
+				out, err := oc.Run("start-build").Args(bcName).Output()
202
+				o.Expect(err).NotTo(o.HaveOccurred())
203
+				startedBuilds = append(startedBuilds, strings.TrimSpace(out))
204
+			}
205
+			// Verify that the first build will complete and the next build to run
206
+			// will be the last build created.
207
+			for {
208
+				event := <-buildWatch.ResultChan()
209
+				build := event.Object.(*buildapi.Build)
210
+				// The second build should be cancelled
211
+				if build.Status.Phase == buildapi.BuildPhaseCancelled {
212
+					if build.Name == startedBuilds[1] {
213
+						buildVerified[build.Name] = true
214
+						wasCancelled = true
215
+						counter++
216
+					}
217
+				}
218
+				// Only first and third build should actually run (serially).
219
+				if build.Status.Phase == buildapi.BuildPhaseRunning {
220
+					// Ignore events from complete builds (if there are any) if we already
221
+					// verified the build.
222
+					if _, exists := buildVerified[build.Name]; exists {
223
+						continue
224
+					}
225
+					// Verify there are no other running or pending builds than this
226
+					// build as serial build always runs alone.
227
+					c := buildclient.NewOSClientBuildClient(oc.REST())
228
+					builds, err := buildutil.BuildConfigBuilds(c, oc.Namespace(), bcName, func(b buildapi.Build) bool {
229
+						fmt.Printf("[%s] build %s is %s", build.Name, b.Name, b.Status.Phase)
230
+						if b.Name == build.Name {
231
+							return false
232
+						}
233
+						if b.Status.Phase == buildapi.BuildPhaseRunning || b.Status.Phase == buildapi.BuildPhasePending {
234
+							return true
235
+						}
236
+						return false
237
+					})
238
+					o.Expect(err).NotTo(o.HaveOccurred())
239
+					o.Expect(builds.Items).Should(o.BeEmpty())
240
+
241
+					// The builds should start in the same order as they were created.
242
+					o.Expect(build.Name).Should(o.BeEquivalentTo(startedBuilds[counter]))
243
+
244
+					buildVerified[build.Name] = true
245
+					counter++
246
+				}
247
+				if len(buildVerified) == len(startedBuilds) {
248
+					break
249
+				}
250
+			}
251
+
252
+			o.Expect(wasCancelled).Should(o.BeEquivalentTo(true))
253
+		})
254
+	})
255
+
256
+})
0 257
new file mode 100644
... ...
@@ -0,0 +1,39 @@
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
+        creationTimestamp: null
11
+      spec: {}
12
+      status: 
13
+        dockerImageRepository: ""
14
+    - 
15
+      kind: "BuildConfig"
16
+      apiVersion: "v1"
17
+      metadata: 
18
+        name: "sample-parallel-build"
19
+      spec: 
20
+        runPolicy: "Parallel"
21
+        triggers: 
22
+          - 
23
+            type: "imageChange"
24
+            imageChange: {}
25
+        source: 
26
+          type: "Git"
27
+          git: 
28
+            uri: "git://github.com/openshift/ruby-hello-world.git"
29
+        strategy: 
30
+          type: "Source"
31
+          sourceStrategy: 
32
+            from: 
33
+              kind: "DockerImage"
34
+              name: "centos/ruby-22-centos7"
35
+        resources: {}
36
+      status: 
37
+        lastVersion: 0
38
+
0 39
new file mode 100644
... ...
@@ -0,0 +1,30 @@
0
+---
1
+  kind: "List"
2
+  apiVersion: "v1"
3
+  metadata: {}
4
+  items: 
5
+    - 
6
+      kind: "BuildConfig"
7
+      apiVersion: "v1"
8
+      metadata: 
9
+        name: "sample-serial-build"
10
+      spec: 
11
+        runPolicy: "Serial"
12
+        triggers: 
13
+          - 
14
+            type: "imageChange"
15
+            imageChange: {}
16
+        source: 
17
+          type: "Git"
18
+          git: 
19
+            uri: "git://github.com/openshift/ruby-hello-world.git"
20
+        strategy: 
21
+          type: "Source"
22
+          sourceStrategy: 
23
+            from: 
24
+              kind: "DockerImage"
25
+              name: "centos/ruby-22-centos7"
26
+        resources: {}
27
+      status: 
28
+        lastVersion: 0
29
+
0 30
new file mode 100644
... ...
@@ -0,0 +1,30 @@
0
+---
1
+  kind: "List"
2
+  apiVersion: "v1"
3
+  metadata: {}
4
+  items: 
5
+    - 
6
+      kind: "BuildConfig"
7
+      apiVersion: "v1"
8
+      metadata: 
9
+        name: "sample-serial-latest-only-build"
10
+      spec: 
11
+        runPolicy: "SerialLatestOnly"
12
+        triggers: 
13
+          - 
14
+            type: "imageChange"
15
+            imageChange: {}
16
+        source: 
17
+          type: "Git"
18
+          git: 
19
+            uri: "git://github.com/openshift/ruby-hello-world.git"
20
+        strategy: 
21
+          type: "Source"
22
+          sourceStrategy: 
23
+            from: 
24
+              kind: "DockerImage"
25
+              name: "centos/ruby-22-centos7"
26
+        resources: {}
27
+      status: 
28
+        lastVersion: 0
29
+
... ...
@@ -125,7 +125,12 @@ func admissionTestPod() *kapi.Pod {
125 125
 }
126 126
 
127 127
 func admissionTestBuild() *buildapi.Build {
128
-	build := &buildapi.Build{}
128
+	build := &buildapi.Build{ObjectMeta: kapi.ObjectMeta{
129
+		Labels: map[string]string{
130
+			buildapi.BuildConfigLabel:    "mock-build-config",
131
+			buildapi.BuildRunPolicyLabel: string(buildapi.BuildRunPolicyParallel),
132
+		},
133
+	}}
129 134
 	build.Name = "test-build"
130 135
 	build.Spec.Source.Git = &buildapi.GitBuildSource{URI: "http://build.uri/build"}
131 136
 	build.Spec.Strategy.DockerStrategy = &buildapi.DockerBuildStrategy{}
... ...
@@ -249,6 +249,10 @@ func strategyForType(t *testing.T, strategy string) buildapi.BuildStrategy {
249 249
 
250 250
 func createBuild(t *testing.T, buildInterface client.BuildInterface, strategy string) (*buildapi.Build, error) {
251 251
 	build := &buildapi.Build{}
252
+	build.ObjectMeta.Labels = map[string]string{
253
+		buildapi.BuildConfigLabel:    "mock-build-config",
254
+		buildapi.BuildRunPolicyLabel: string(buildapi.BuildRunPolicyParallel),
255
+	}
252 256
 	build.GenerateName = strings.ToLower(string(strategy)) + "-build-"
253 257
 	build.Spec.Strategy = strategyForType(t, strategy)
254 258
 	build.Spec.Source.Git = &buildapi.GitBuildSource{URI: "example.org"}
... ...
@@ -263,6 +267,7 @@ func updateBuild(t *testing.T, buildInterface client.BuildInterface, build *buil
263 263
 
264 264
 func createBuildConfig(t *testing.T, buildConfigInterface client.BuildConfigInterface, strategy string) (*buildapi.BuildConfig, error) {
265 265
 	buildConfig := &buildapi.BuildConfig{}
266
+	buildConfig.Spec.RunPolicy = buildapi.BuildRunPolicyParallel
266 267
 	buildConfig.GenerateName = strings.ToLower(string(strategy)) + "-buildconfig-"
267 268
 	buildConfig.Spec.Strategy = strategyForType(t, strategy)
268 269
 	buildConfig.Spec.Source.Git = &buildapi.GitBuildSource{URI: "example.org"}
... ...
@@ -51,8 +51,10 @@ func mockBuild() *buildapi.Build {
51 51
 		ObjectMeta: kapi.ObjectMeta{
52 52
 			GenerateName: "mock-build",
53 53
 			Labels: map[string]string{
54
-				"label1": "value1",
55
-				"label2": "value2",
54
+				"label1":                     "value1",
55
+				"label2":                     "value2",
56
+				buildapi.BuildConfigLabel:    "mock-build-config",
57
+				buildapi.BuildRunPolicyLabel: string(buildapi.BuildRunPolicyParallel),
56 58
 			},
57 59
 		},
58 60
 		Spec: buildapi.BuildSpec{
... ...
@@ -92,7 +92,12 @@ func TestBuildOverrideForcePullCustomStrategy(t *testing.T) {
92 92
 }
93 93
 
94 94
 func buildPodAdmissionTestCustomBuild() *buildapi.Build {
95
-	build := &buildapi.Build{}
95
+	build := &buildapi.Build{ObjectMeta: kapi.ObjectMeta{
96
+		Labels: map[string]string{
97
+			buildapi.BuildConfigLabel:    "mock-build-config",
98
+			buildapi.BuildRunPolicyLabel: string(buildapi.BuildRunPolicyParallel),
99
+		},
100
+	}}
96 101
 	build.Name = "test-custom-build"
97 102
 	build.Spec.Source.Git = &buildapi.GitBuildSource{URI: "http://test/src"}
98 103
 	build.Spec.Strategy.CustomStrategy = &buildapi.CustomBuildStrategy{}
... ...
@@ -102,7 +107,12 @@ func buildPodAdmissionTestCustomBuild() *buildapi.Build {
102 102
 }
103 103
 
104 104
 func buildPodAdmissionTestDockerBuild() *buildapi.Build {
105
-	build := &buildapi.Build{}
105
+	build := &buildapi.Build{ObjectMeta: kapi.ObjectMeta{
106
+		Labels: map[string]string{
107
+			buildapi.BuildConfigLabel:    "mock-build-config",
108
+			buildapi.BuildRunPolicyLabel: string(buildapi.BuildRunPolicyParallel),
109
+		},
110
+	}}
106 111
 	build.Name = "test-build"
107 112
 	build.Spec.Source.Git = &buildapi.GitBuildSource{URI: "http://test/src"}
108 113
 	build.Spec.Strategy.DockerStrategy = &buildapi.DockerBuildStrategy{}
... ...
@@ -113,6 +113,7 @@ func imageChangeBuildConfig(name string, strategy buildapi.BuildStrategy) *build
113 113
 			Labels:    map[string]string{"testlabel": "testvalue"},
114 114
 		},
115 115
 		Spec: buildapi.BuildConfigSpec{
116
+			RunPolicy: buildapi.BuildRunPolicyParallel,
116 117
 			BuildSpec: buildapi.BuildSpec{
117 118
 				Source: buildapi.BuildSource{
118 119
 					Git: &buildapi.GitBuildSource{
... ...
@@ -116,7 +116,14 @@ func TestProjectMustExist(t *testing.T) {
116 116
 	}
117 117
 
118 118
 	build := &buildapi.Build{
119
-		ObjectMeta: kapi.ObjectMeta{Name: "buildid", Namespace: "default"},
119
+		ObjectMeta: kapi.ObjectMeta{
120
+			Name:      "buildid",
121
+			Namespace: "default",
122
+			Labels: map[string]string{
123
+				buildapi.BuildConfigLabel:    "mock-build-config",
124
+				buildapi.BuildRunPolicyLabel: string(buildapi.BuildRunPolicyParallel),
125
+			},
126
+		},
120 127
 		Spec: buildapi.BuildSpec{
121 128
 			Source: buildapi.BuildSource{
122 129
 				Git: &buildapi.GitBuildSource{
... ...
@@ -298,6 +298,7 @@ func mockBuildConfigImageParms(imageName, imageStream, imageTag string) *buildap
298 298
 			Name: "pushbuild",
299 299
 		},
300 300
 		Spec: buildapi.BuildConfigSpec{
301
+			RunPolicy: buildapi.BuildRunPolicyParallel,
301 302
 			Triggers: []buildapi.BuildTriggerPolicy{
302 303
 				{
303 304
 					Type: buildapi.GitHubWebHookBuildTriggerType,
... ...
@@ -338,6 +339,7 @@ func mockBuildConfigImageStreamParms(imageName, imageStream, imageTag string) *b
338 338
 			Name: "pushbuild",
339 339
 		},
340 340
 		Spec: buildapi.BuildConfigSpec{
341
+			RunPolicy: buildapi.BuildRunPolicyParallel,
341 342
 			Triggers: []buildapi.BuildTriggerPolicy{
342 343
 				{
343 344
 					Type: buildapi.GitHubWebHookBuildTriggerType,