Skip to content

Commit

Permalink
Merge pull request openshift#6185 from mnagy/build_config_reaper
Browse files Browse the repository at this point in the history
Merged by openshift-bot
  • Loading branch information
OpenShift Bot committed Jan 19, 2016
2 parents 07529cf + c765bd0 commit d43e029
Show file tree
Hide file tree
Showing 12 changed files with 464 additions and 4 deletions.
3 changes: 3 additions & 0 deletions pkg/build/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,9 @@ const (
// BuildConfigLabelDeprecated was used as BuildConfigLabel before adding namespaces.
// We keep it for backward compatibility.
BuildConfigLabelDeprecated = "buildconfig"
// BuildConfigPausedAnnotation is an annotation that marks a BuildConfig as paused.
// New Builds cannot be instantiated from a paused BuildConfig.
BuildConfigPausedAnnotation = "openshift.io/build-config.paused"
)

// BuildConfig is a template which can be used to create new builds.
Expand Down
21 changes: 21 additions & 0 deletions pkg/build/controller/config_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,27 @@ import (

buildapi "github.com/openshift/origin/pkg/build/api"
buildclient "github.com/openshift/origin/pkg/build/client"
buildgenerator "github.com/openshift/origin/pkg/build/generator"
)

// ConfigControllerFatalError represents a fatal error while generating a build.
// An operation that fails because of a fatal error should not be retried.
type ConfigControllerFatalError struct {
// Reason the fatal error occurred
Reason string
}

// Error returns the error string for this fatal error
func (e ConfigControllerFatalError) Error() string {
return fmt.Sprintf("fatal error processing BuildConfig: %s", e.Reason)
}

// IsFatal returns true if err is a fatal error
func IsFatal(err error) bool {
_, isFatal := err.(ConfigControllerFatalError)
return isFatal
}

type BuildConfigController struct {
BuildConfigInstantiator buildclient.BuildConfigInstantiator
}
Expand Down Expand Up @@ -50,6 +69,8 @@ func (c *BuildConfigController) HandleBuildConfig(bc *buildapi.BuildConfig) erro
if kerrors.IsConflict(err) {
instantiateErr = fmt.Errorf("unable to instantiate Build for BuildConfig %s/%s due to a conflicting update: %v", bc.Namespace, bc.Name, err)
util.HandleError(instantiateErr)
} else if buildgenerator.IsFatal(err) {
return &ConfigControllerFatalError{err.Error()}
} else {
instantiateErr = fmt.Errorf("error instantiating Build from BuildConfig %s/%s: %v", bc.Namespace, bc.Name, err)
util.HandleError(instantiateErr)
Expand Down
2 changes: 1 addition & 1 deletion pkg/build/controller/factory/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ func (factory *BuildConfigControllerFactory) Create() controller.RunnableControl
RetryManager: controller.NewQueueRetryManager(
queue,
cache.MetaNamespaceKeyFunc,
retryFunc("BuildConfig", nil),
retryFunc("BuildConfig", buildcontroller.IsFatal),
kutil.NewTokenBucketRateLimiter(1, 10)),
Handle: func(obj interface{}) error {
bc := obj.(*buildapi.BuildConfig)
Expand Down
22 changes: 22 additions & 0 deletions pkg/build/generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,24 @@ import (
"github.com/openshift/origin/pkg/util/namer"
)

// GeneratorFatalError represents a fatal error while generating a build.
// An operation that fails because of a fatal error should not be retried.
type GeneratorFatalError struct {
// Reason the fatal error occurred
Reason string
}

// Error returns the error string for this fatal error
func (e GeneratorFatalError) Error() string {
return fmt.Sprintf("fatal error generating Build from BuildConfig: %s", e.Reason)
}

// IsFatal returns true if err is a fatal error
func IsFatal(err error) bool {
_, isFatal := err.(GeneratorFatalError)
return isFatal
}

// BuildGenerator is a central place responsible for generating new Build objects
// from BuildConfigs and other Builds.
type BuildGenerator struct {
Expand Down Expand Up @@ -199,6 +217,10 @@ func (g *BuildGenerator) Instantiate(ctx kapi.Context, request *buildapi.BuildRe
return nil, err
}

if strings.ToLower(bc.Annotations[buildapi.BuildConfigPausedAnnotation]) == "true" {
return nil, &GeneratorFatalError{fmt.Sprintf("can't instantiate from BuildConfig %s/%s: BuildConfig is paused", bc.Namespace, bc.Name)}
}

if err := g.checkLastVersion(bc, request.LastVersion); err != nil {
return nil, err
}
Expand Down
20 changes: 20 additions & 0 deletions pkg/build/generator/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,26 @@ func TestInstantiateRetry(t *testing.T) {
}
*/

func TestInstantiateDeletingError(t *testing.T) {
generator := BuildGenerator{Client: Client{
GetBuildConfigFunc: func(ctx kapi.Context, name string) (*buildapi.BuildConfig, error) {
bc := &buildapi.BuildConfig{
ObjectMeta: kapi.ObjectMeta{
Annotations: map[string]string{
buildapi.BuildConfigPausedAnnotation: "true",
},
},
}
return bc, nil
},
}}

_, err := generator.Instantiate(kapi.NewDefaultContext(), &buildapi.BuildRequest{})
if err == nil || !strings.Contains(err.Error(), "BuildConfig is paused") {
t.Errorf("Expected error, got different %v", err)
}
}

func TestInstantiateGetBuildConfigError(t *testing.T) {
generator := BuildGenerator{Client: Client{
GetBuildConfigFunc: func(ctx kapi.Context, name string) (*buildapi.BuildConfig, error) {
Expand Down
2 changes: 2 additions & 0 deletions pkg/build/reaper/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package reaper implements the Reaper interface for buildConfigs
package reaper
122 changes: 122 additions & 0 deletions pkg/build/reaper/reaper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package reaper

import (
"fmt"
"strings"
"time"

"github.com/golang/glog"
kapi "k8s.io/kubernetes/pkg/api"
kerrors "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/kubectl"
kutilerrors "k8s.io/kubernetes/pkg/util/errors"

buildapi "github.com/openshift/origin/pkg/build/api"
buildutil "github.com/openshift/origin/pkg/build/util"
"github.com/openshift/origin/pkg/client"
"github.com/openshift/origin/pkg/util"
)

// NewBuildConfigReaper returns a new reaper for buildConfigs
func NewBuildConfigReaper(oc *client.Client) kubectl.Reaper {
return &BuildConfigReaper{oc: oc, pollInterval: kubectl.Interval, timeout: kubectl.Timeout}
}

// BuildConfigReaper implements the Reaper interface for buildConfigs
type BuildConfigReaper struct {
oc client.Interface
pollInterval, timeout time.Duration
}

// Stop deletes the build configuration and all of the associated builds.
func (reaper *BuildConfigReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *kapi.DeleteOptions) (string, error) {
noBcFound := false
noBuildFound := true

// Add deletion pending annotation to the build config
err := unversioned.RetryOnConflict(unversioned.DefaultRetry, func() error {
bc, err := reaper.oc.BuildConfigs(namespace).Get(name)
if kerrors.IsNotFound(err) {
noBcFound = true
return nil
}
if err != nil {
return err
}

// Ignore if the annotation already exists
if strings.ToLower(bc.Annotations[buildapi.BuildConfigPausedAnnotation]) == "true" {
return nil
}

// Set the annotation and update
if err := util.AddObjectAnnotations(bc, map[string]string{buildapi.BuildConfigPausedAnnotation: "true"}); err != nil {
return err
}
_, err = reaper.oc.BuildConfigs(namespace).Update(bc)
return err
})
if err != nil {
return "", err
}

// Warn the user if the BuildConfig won't get deleted after this point.
bcDeleted := false
defer func() {
if !bcDeleted {
glog.Warningf("BuildConfig %s/%s will not be deleted because not all associated builds could be deleted. You can try re-running the command or removing them manually", namespace, name)
}
}()

// Collect builds related to the config.
builds, err := reaper.oc.Builds(namespace).List(buildutil.BuildConfigSelector(name), nil)
if err != nil {
return "", err
}
errList := []error{}
for _, build := range builds.Items {
noBuildFound = false
if err := reaper.oc.Builds(namespace).Delete(build.Name); err != nil {
glog.Warningf("Cannot delete Build %s/%s: %v", build.Namespace, build.Name, err)
if !kerrors.IsNotFound(err) {
errList = append(errList, err)
}
}
}

// Collect deprecated builds related to the config.
// TODO: Delete this block after BuildConfigLabelDeprecated is removed.
builds, err = reaper.oc.Builds(namespace).List(buildutil.BuildConfigSelectorDeprecated(name), nil)
if err != nil {
return "", err
}
for _, build := range builds.Items {
noBuildFound = false
if err := reaper.oc.Builds(namespace).Delete(build.Name); err != nil {
glog.Warningf("Cannot delete Build %s/%s: %v", build.Namespace, build.Name, err)
if !kerrors.IsNotFound(err) {
errList = append(errList, err)
}
}
}

// Aggregate all errors
if len(errList) > 0 {
return "", kutilerrors.NewAggregate(errList)
}

// Finally we can delete the BuildConfig
if !noBcFound {
if err := reaper.oc.BuildConfigs(namespace).Delete(name); err != nil {
return "", err
}
}
bcDeleted = true

if noBcFound && noBuildFound {
return "", kerrors.NewNotFound("BuildConfig", name)
}

return fmt.Sprintf("%s stopped", name), nil
}
Loading

0 comments on commit d43e029

Please sign in to comment.