// GetNextConfigBuild returns the build that will be executed next for the given // build configuration. It also returns the indication whether there are // currently running builds, to make sure there is no race-condition between // re-listing the builds. func GetNextConfigBuild(lister buildclient.BuildLister, namespace, buildConfigName string) (*buildapi.Build, bool, error) { var ( nextBuild *buildapi.Build hasRunningBuilds bool previousBuildNumber int64 ) builds, err := buildutil.BuildConfigBuilds(lister, namespace, buildConfigName, func(b buildapi.Build) bool { switch b.Status.Phase { case buildapi.BuildPhasePending, buildapi.BuildPhaseRunning: hasRunningBuilds = true return false } // Only 'new' build can be scheduled to run next return b.Status.Phase == buildapi.BuildPhaseNew }) if err != nil { return nil, hasRunningBuilds, err } for i, b := range builds.Items { buildNumber, err := buildutil.BuildNumber(&b) if err != nil { return nil, hasRunningBuilds, err } if previousBuildNumber == 0 || buildNumber < previousBuildNumber { nextBuild = &builds.Items[i] previousBuildNumber = buildNumber } } return nextBuild, hasRunningBuilds, nil }
// hasRunningSerialBuild indicates that there is a running or pending serial // build. This function is used to prevent running parallel builds because // serial builds should always run alone. func hasRunningSerialBuild(lister buildclient.BuildLister, namespace, buildConfigName string) bool { var hasRunningBuilds bool buildutil.BuildConfigBuilds(lister, namespace, buildConfigName, func(b buildapi.Build) bool { switch b.Status.Phase { case buildapi.BuildPhasePending, buildapi.BuildPhaseRunning: switch buildutil.BuildRunPolicy(&b) { case buildapi.BuildRunPolicySerial, buildapi.BuildRunPolicySerialLatestOnly: hasRunningBuilds = true } } return false }) return hasRunningBuilds }
// GetNextConfigBuild returns the build that will be executed next for the given // build configuration. It also returns the indication whether there are // currently running builds, to make sure there is no race-condition between // re-listing the builds. func GetNextConfigBuild(lister buildclient.BuildLister, namespace, buildConfigName string) ([]*buildapi.Build, bool, error) { var ( nextBuild *buildapi.Build hasRunningBuilds bool previousBuildNumber int64 ) builds, err := buildutil.BuildConfigBuilds(lister, namespace, buildConfigName, func(b buildapi.Build) bool { switch b.Status.Phase { case buildapi.BuildPhasePending, buildapi.BuildPhaseRunning: hasRunningBuilds = true return false } // Only 'new' build can be scheduled to run next return b.Status.Phase == buildapi.BuildPhaseNew }) if err != nil { return nil, hasRunningBuilds, err } nextParallelBuilds := []*buildapi.Build{} for i, b := range builds.Items { buildNumber, err := buildutil.BuildNumber(&b) if err != nil { return nil, hasRunningBuilds, err } if buildutil.BuildRunPolicy(&b) == buildapi.BuildRunPolicyParallel { nextParallelBuilds = append(nextParallelBuilds, &builds.Items[i]) } if previousBuildNumber == 0 || buildNumber < previousBuildNumber { nextBuild = &builds.Items[i] previousBuildNumber = buildNumber } } nextBuilds := []*buildapi.Build{} // if the next build is a parallel build, then start all the queued parallel builds, // otherwise just start the next build if there is one. if nextBuild != nil && buildutil.BuildRunPolicy(nextBuild) == buildapi.BuildRunPolicyParallel { nextBuilds = nextParallelBuilds } else if nextBuild != nil { nextBuilds = append(nextBuilds, nextBuild) } return nextBuilds, hasRunningBuilds, nil }
// cancelPreviousBuilds cancels all queued builds that have the build sequence number // lower than the given build. It retries the cancellation in case of conflict. func (s *SerialLatestOnlyPolicy) cancelPreviousBuilds(build *buildapi.Build) []error { bcName := buildutil.ConfigNameForBuild(build) if len(bcName) == 0 { return []error{} } currentBuildNumber, err := buildutil.BuildNumber(build) if err != nil { return []error{NewNoBuildNumberAnnotationError(build)} } builds, err := buildutil.BuildConfigBuilds(s.BuildLister, build.Namespace, bcName, func(b buildapi.Build) bool { // Do not cancel the complete builds, builds that were already cancelled, or // running builds. if buildutil.IsBuildComplete(&b) || b.Status.Phase == buildapi.BuildPhaseRunning { return false } // Prevent race-condition when there is a newer build than this and we don't // want to cancel it. The HandleBuild() function that runs for that build // will cancel this build. buildNumber, _ := buildutil.BuildNumber(&b) return buildNumber < currentBuildNumber }) if err != nil { return []error{err} } var result = []error{} for _, b := range builds.Items { err := wait.Poll(500*time.Millisecond, 5*time.Second, func() (bool, error) { b.Status.Cancelled = true err := s.BuildUpdater.Update(b.Namespace, &b) if err != nil && errors.IsConflict(err) { glog.V(5).Infof("Error cancelling build %s/%s: %v (will retry)", b.Namespace, b.Name, err) return false, nil } return true, err }) if err != nil { result = append(result, err) } } return result }
break } continue } // When the the other two builds we started after waiting for the first // build to become running are Pending, verify the first build is still // running (so the other two builds are started in parallel with first // build). // TODO: This might introduce flakes in case the first build complete // sooner or fail. if build.Status.Phase == buildapi.BuildPhasePending { c := buildclient.NewOSClientBuildClient(oc.Client()) firstBuildRunning := false _, err := buildutil.BuildConfigBuilds(c, oc.Namespace(), bcName, func(b buildapi.Build) bool { if b.Name == startedBuilds[0] && b.Status.Phase == buildapi.BuildPhaseRunning { firstBuildRunning = true } return false }) o.Expect(err).NotTo(o.HaveOccurred()) o.Expect(firstBuildRunning).Should(o.BeTrue()) counter++ } // When the build failed or completed prematurely, fail the test o.Expect(buildutil.IsBuildComplete(build)).Should(o.BeFalse()) if counter == 2 { break } } o.Expect(counter).Should(o.BeEquivalentTo(2)) }) })
// Complete completes all the required options. func (o *CancelBuildOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, args []string, in io.Reader, out io.Writer) error { o.In = in o.Out = out o.ErrOut = cmd.OutOrStderr() o.ReportError = func(err error) { o.HasError = true fmt.Fprintf(o.ErrOut, "error: %s\n", err.Error()) } if len(args) == 0 { return kcmdutil.UsageError(cmd, "Must pass a name of a build or a buildconfig to cancel") } namespace, _, err := f.DefaultNamespace() if err != nil { return err } if len(o.States) == 0 { // If --state is not specified, set the default to "new", "pending" and // "running". o.States = []string{"new", "pending", "running"} } else { for _, state := range o.States { if len(state) > 0 && !isStateCancellable(state) { return kcmdutil.UsageError(cmd, "The '--state' flag has invalid value. Must be one of 'new', 'pending', or 'running'") } } } client, _, err := f.Clients() if err != nil { return err } o.Namespace = namespace o.Client = client o.BuildLister = buildclient.NewOSClientBuildClient(client) o.BuildClient = client.Builds(namespace) o.Mapper, _ = f.Object(false) for _, item := range args { resource, name, err := cmdutil.ResolveResource(buildapi.Resource("builds"), item, o.Mapper) if err != nil { return err } switch resource { case buildapi.Resource("buildconfigs"): list, err := buildutil.BuildConfigBuilds(o.BuildLister, o.Namespace, name, nil) if err != nil { return err } for _, b := range list.Items { o.BuildNames = append(o.BuildNames, b.Name) } case buildapi.Resource("builds"): o.BuildNames = append(o.BuildNames, strings.TrimSpace(name)) default: return fmt.Errorf("invalid resource provided: %v", resource) } } return nil }