| ... | ... |
@@ -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, |