| ... | ... |
@@ -8,7 +8,6 @@ import ( |
| 8 | 8 |
kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
| 9 | 9 |
errors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" |
| 10 | 10 |
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache" |
| 11 |
- "github.com/GoogleCloudPlatform/kubernetes/pkg/util" |
|
| 12 | 11 |
|
| 13 | 12 |
buildapi "github.com/openshift/origin/pkg/build/api" |
| 14 | 13 |
buildclient "github.com/openshift/origin/pkg/build/client" |
| ... | ... |
@@ -17,9 +16,6 @@ import ( |
| 17 | 17 |
|
| 18 | 18 |
// BuildController watches build resources and manages their state |
| 19 | 19 |
type BuildController struct {
|
| 20 |
- BuildStore cache.Store |
|
| 21 |
- NextBuild func() *buildapi.Build |
|
| 22 |
- NextPod func() *kapi.Pod |
|
| 23 | 20 |
BuildUpdater buildclient.BuildUpdater |
| 24 | 21 |
PodManager podManager |
| 25 | 22 |
BuildStrategy BuildStrategy |
| ... | ... |
@@ -41,32 +37,30 @@ type imageRepositoryClient interface {
|
| 41 | 41 |
GetImageRepository(namespace, name string) (*imageapi.ImageRepository, error) |
| 42 | 42 |
} |
| 43 | 43 |
|
| 44 |
-// Run begins watching and syncing build jobs onto the cluster. |
|
| 45 |
-func (bc *BuildController) Run() {
|
|
| 46 |
- go util.Forever(func() { bc.HandleBuild(bc.NextBuild()) }, 0)
|
|
| 47 |
- go util.Forever(func() { bc.HandlePod(bc.NextPod()) }, 0)
|
|
| 48 |
-} |
|
| 49 |
- |
|
| 50 |
-func (bc *BuildController) HandleBuild(build *buildapi.Build) {
|
|
| 44 |
+func (bc *BuildController) HandleBuild(build *buildapi.Build) error {
|
|
| 51 | 45 |
glog.V(4).Infof("Handling build %s", build.Name)
|
| 52 | 46 |
|
| 53 | 47 |
// We only deal with new builds here |
| 54 | 48 |
if build.Status != buildapi.BuildStatusNew {
|
| 55 |
- return |
|
| 49 |
+ return nil |
|
| 56 | 50 |
} |
| 57 | 51 |
|
| 58 | 52 |
if err := bc.nextBuildStatus(build); err != nil {
|
| 59 | 53 |
// TODO: all build errors should be retried, and build error should not be a permanent status change. |
| 60 | 54 |
// Instead, we should requeue this build request using the same backoff logic as the scheduler. |
| 61 |
- // BuildStatusError should be reserved for meaning "permanently errored, no way to try again". |
|
| 62 |
- glog.V(4).Infof("Build failed with error %s/%s: %#v", build.Namespace, build.Name, err)
|
|
| 63 |
- build.Status = buildapi.BuildStatusError |
|
| 64 |
- build.Message = err.Error() |
|
| 55 |
+ //build.Status = buildapi.BuildStatusError |
|
| 56 |
+ //build.Message = err.Error() |
|
| 57 |
+ return fmt.Errorf("Build failed with error %s/%s: %#v", build.Namespace, build.Name, err)
|
|
| 65 | 58 |
} |
| 66 | 59 |
|
| 67 | 60 |
if err := bc.BuildUpdater.Update(build.Namespace, build); err != nil {
|
| 61 |
+ // This is not a retryable error because the build has been created. The worst case |
|
| 62 |
+ // outcome of not updating the buildconfig is that we might rerun a build for the |
|
| 63 |
+ // same "new" imageid change in the future, which is better than guaranteeing we |
|
| 64 |
+ // run the build 2+ times by retrying it here. |
|
| 68 | 65 |
glog.V(2).Infof("Failed to record changes to build %s/%s: %#v", build.Namespace, build.Name, err)
|
| 69 | 66 |
} |
| 67 |
+ return nil |
|
| 70 | 68 |
} |
| 71 | 69 |
|
| 72 | 70 |
// nextBuildStatus updates build with any appropriate changes, or returns an error if |
| ... | ... |
@@ -134,7 +128,14 @@ func (bc *BuildController) nextBuildStatus(build *buildapi.Build) error {
|
| 134 | 134 |
return nil |
| 135 | 135 |
} |
| 136 | 136 |
|
| 137 |
-func (bc *BuildController) HandlePod(pod *kapi.Pod) {
|
|
| 137 |
+// BuildPodController watches pods running builds and manages the build state |
|
| 138 |
+type BuildPodController struct {
|
|
| 139 |
+ BuildStore cache.Store |
|
| 140 |
+ BuildUpdater buildclient.BuildUpdater |
|
| 141 |
+ PodManager podManager |
|
| 142 |
+} |
|
| 143 |
+ |
|
| 144 |
+func (bc *BuildPodController) HandlePod(pod *kapi.Pod) error {
|
|
| 138 | 145 |
// Find the build for this pod |
| 139 | 146 |
var build *buildapi.Build |
| 140 | 147 |
for _, obj := range bc.BuildStore.List() {
|
| ... | ... |
@@ -146,7 +147,7 @@ func (bc *BuildController) HandlePod(pod *kapi.Pod) {
|
| 146 | 146 |
} |
| 147 | 147 |
|
| 148 | 148 |
if build == nil {
|
| 149 |
- return |
|
| 149 |
+ return nil |
|
| 150 | 150 |
} |
| 151 | 151 |
|
| 152 | 152 |
// A cancelling event was triggered for the build, delete its pod and update build status. |
| ... | ... |
@@ -154,9 +155,9 @@ func (bc *BuildController) HandlePod(pod *kapi.Pod) {
|
| 154 | 154 |
glog.V(2).Infof("Cancelling build %s.", build.Name)
|
| 155 | 155 |
|
| 156 | 156 |
if err := bc.CancelBuild(build, pod); err != nil {
|
| 157 |
- glog.Errorf("Failed to cancel build %s: %#v", build.Name, err)
|
|
| 157 |
+ return fmt.Errorf("Failed to cancel build %s: %#v, will retry", build.Name, err)
|
|
| 158 | 158 |
} |
| 159 |
- return |
|
| 159 |
+ return nil |
|
| 160 | 160 |
} |
| 161 | 161 |
|
| 162 | 162 |
nextStatus := build.Status |
| ... | ... |
@@ -180,13 +181,14 @@ func (bc *BuildController) HandlePod(pod *kapi.Pod) {
|
| 180 | 180 |
glog.V(4).Infof("Updating build %s status %s -> %s", build.Name, build.Status, nextStatus)
|
| 181 | 181 |
build.Status = nextStatus |
| 182 | 182 |
if err := bc.BuildUpdater.Update(build.Namespace, build); err != nil {
|
| 183 |
- glog.Errorf("Failed to update build %s: %#v", build.Name, err)
|
|
| 183 |
+ return fmt.Errorf("Failed to update build %s: %#v", build.Name, err)
|
|
| 184 | 184 |
} |
| 185 | 185 |
} |
| 186 |
+ return nil |
|
| 186 | 187 |
} |
| 187 | 188 |
|
| 188 |
-// CancelBuild updates a build status to Cancelled, after its associated pod is associated. |
|
| 189 |
-func (bc *BuildController) CancelBuild(build *buildapi.Build, pod *kapi.Pod) error {
|
|
| 189 |
+// CancelBuild updates a build status to Cancelled, after its associated pod is deleted. |
|
| 190 |
+func (bc *BuildPodController) CancelBuild(build *buildapi.Build, pod *kapi.Pod) error {
|
|
| 190 | 191 |
if !isBuildCancellable(build) {
|
| 191 | 192 |
glog.V(2).Infof("The build can be cancelled only if it has pending/running status, not %s.", build.Status)
|
| 192 | 193 |
return nil |
| ... | ... |
@@ -2,6 +2,7 @@ package controller |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"errors" |
| 5 |
+ "reflect" |
|
| 5 | 6 |
"testing" |
| 6 | 7 |
|
| 7 | 8 |
kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
| ... | ... |
@@ -93,8 +94,8 @@ func (_ *errNotFoundImageRepositoryClient) GetImageRepository(namespace, name st |
| 93 | 93 |
return nil, kerrors.NewNotFound("ImageRepository", name)
|
| 94 | 94 |
} |
| 95 | 95 |
|
| 96 |
-func mockBuildAndController(status buildapi.BuildStatus, output buildapi.BuildOutput) (build *buildapi.Build, controller *BuildController) {
|
|
| 97 |
- build = &buildapi.Build{
|
|
| 96 |
+func mockBuild(status buildapi.BuildStatus, output buildapi.BuildOutput) *buildapi.Build {
|
|
| 97 |
+ return &buildapi.Build{
|
|
| 98 | 98 |
ObjectMeta: kapi.ObjectMeta{
|
| 99 | 99 |
Name: "data-build", |
| 100 | 100 |
Namespace: "namespace", |
| ... | ... |
@@ -119,16 +120,23 @@ func mockBuildAndController(status buildapi.BuildStatus, output buildapi.BuildOu |
| 119 | 119 |
Status: status, |
| 120 | 120 |
PodName: "-the-pod-id", |
| 121 | 121 |
} |
| 122 |
- controller = &BuildController{
|
|
| 123 |
- BuildStore: buildtest.NewFakeBuildStore(build), |
|
| 122 |
+} |
|
| 123 |
+ |
|
| 124 |
+func mockBuildController() *BuildController {
|
|
| 125 |
+ return &BuildController{
|
|
| 124 | 126 |
BuildUpdater: &okBuildUpdater{},
|
| 125 | 127 |
PodManager: &okPodManager{},
|
| 126 |
- NextBuild: func() *buildapi.Build { return nil },
|
|
| 127 |
- NextPod: func() *kapi.Pod { return nil },
|
|
| 128 | 128 |
BuildStrategy: &okStrategy{},
|
| 129 | 129 |
ImageRepositoryClient: &okImageRepositoryClient{},
|
| 130 | 130 |
} |
| 131 |
- return |
|
| 131 |
+} |
|
| 132 |
+ |
|
| 133 |
+func mockBuildPodController(build *buildapi.Build) *BuildPodController {
|
|
| 134 |
+ return &BuildPodController{
|
|
| 135 |
+ BuildStore: buildtest.NewFakeBuildStore(build), |
|
| 136 |
+ BuildUpdater: &okBuildUpdater{},
|
|
| 137 |
+ PodManager: &okPodManager{},
|
|
| 138 |
+ } |
|
| 132 | 139 |
} |
| 133 | 140 |
|
| 134 | 141 |
func mockPod(status kapi.PodPhase, exitCode int) *kapi.Pod {
|
| ... | ... |
@@ -257,7 +265,7 @@ func TestHandleBuild(t *testing.T) {
|
| 257 | 257 |
}, |
| 258 | 258 |
{ // 12
|
| 259 | 259 |
inStatus: buildapi.BuildStatusNew, |
| 260 |
- outStatus: buildapi.BuildStatusError, // TODO: this should be a retry |
|
| 260 |
+ outStatus: buildapi.BuildStatusError, |
|
| 261 | 261 |
imageClient: &errNotFoundImageRepositoryClient{},
|
| 262 | 262 |
buildOutput: buildapi.BuildOutput{
|
| 263 | 263 |
To: &kapi.ObjectReference{
|
| ... | ... |
@@ -275,10 +283,24 @@ func TestHandleBuild(t *testing.T) {
|
| 275 | 275 |
}, |
| 276 | 276 |
}, |
| 277 | 277 |
}, |
| 278 |
+ { // 14
|
|
| 279 |
+ inStatus: buildapi.BuildStatusNew, |
|
| 280 |
+ outStatus: buildapi.BuildStatusPending, |
|
| 281 |
+ buildOutput: buildapi.BuildOutput{
|
|
| 282 |
+ To: &kapi.ObjectReference{
|
|
| 283 |
+ Name: "foo", |
|
| 284 |
+ Namespace: "bar", |
|
| 285 |
+ }, |
|
| 286 |
+ }, |
|
| 287 |
+ outputSpec: "image/repo", |
|
| 288 |
+ // an error updating the build is not reported as an error. |
|
| 289 |
+ buildUpdater: &errBuildUpdater{},
|
|
| 290 |
+ }, |
|
| 278 | 291 |
} |
| 279 | 292 |
|
| 280 | 293 |
for i, tc := range tests {
|
| 281 |
- build, ctrl := mockBuildAndController(tc.inStatus, tc.buildOutput) |
|
| 294 |
+ build := mockBuild(tc.inStatus, tc.buildOutput) |
|
| 295 |
+ ctrl := mockBuildController() |
|
| 282 | 296 |
if tc.buildStrategy != nil {
|
| 283 | 297 |
ctrl.BuildStrategy = tc.buildStrategy |
| 284 | 298 |
} |
| ... | ... |
@@ -292,8 +314,20 @@ func TestHandleBuild(t *testing.T) {
|
| 292 | 292 |
ctrl.ImageRepositoryClient = tc.imageClient |
| 293 | 293 |
} |
| 294 | 294 |
|
| 295 |
- ctrl.HandleBuild(build) |
|
| 295 |
+ err := ctrl.HandleBuild(build) |
|
| 296 |
+ |
|
| 297 |
+ // ensure we return an error for cases where expected output is an error. |
|
| 298 |
+ // these will be retried by the retrycontroller |
|
| 299 |
+ if tc.inStatus != buildapi.BuildStatusError && tc.outStatus == buildapi.BuildStatusError {
|
|
| 300 |
+ if err == nil {
|
|
| 301 |
+ t.Errorf("(%d) Expected an error from HandleBuild, got none!", i)
|
|
| 302 |
+ } |
|
| 303 |
+ continue |
|
| 304 |
+ } |
|
| 296 | 305 |
|
| 306 |
+ if err != nil {
|
|
| 307 |
+ t.Errorf("(%d) Unexpected error %v", err)
|
|
| 308 |
+ } |
|
| 297 | 309 |
if build.Status != tc.outStatus {
|
| 298 | 310 |
t.Errorf("(%d) Expected %s, got %s!", i, tc.outStatus, build.Status)
|
| 299 | 311 |
} |
| ... | ... |
@@ -317,6 +351,7 @@ func TestHandlePod(t *testing.T) {
|
| 317 | 317 |
podStatus kapi.PodPhase |
| 318 | 318 |
exitCode int |
| 319 | 319 |
buildUpdater buildclient.BuildUpdater |
| 320 |
+ podManager podManager |
|
| 320 | 321 |
} |
| 321 | 322 |
|
| 322 | 323 |
tests := []handlePodTest{
|
| ... | ... |
@@ -366,7 +401,8 @@ func TestHandlePod(t *testing.T) {
|
| 366 | 366 |
} |
| 367 | 367 |
|
| 368 | 368 |
for i, tc := range tests {
|
| 369 |
- build, ctrl := mockBuildAndController(tc.inStatus, buildapi.BuildOutput{})
|
|
| 369 |
+ build := mockBuild(tc.inStatus, buildapi.BuildOutput{})
|
|
| 370 |
+ ctrl := mockBuildPodController(build) |
|
| 370 | 371 |
pod := mockPod(tc.podStatus, tc.exitCode) |
| 371 | 372 |
if tc.matchID {
|
| 372 | 373 |
build.PodName = pod.Name |
| ... | ... |
@@ -375,8 +411,16 @@ func TestHandlePod(t *testing.T) {
|
| 375 | 375 |
ctrl.BuildUpdater = tc.buildUpdater |
| 376 | 376 |
} |
| 377 | 377 |
|
| 378 |
- ctrl.HandlePod(pod) |
|
| 378 |
+ err := ctrl.HandlePod(pod) |
|
| 379 | 379 |
|
| 380 |
+ if tc.buildUpdater != nil && reflect.TypeOf(tc.buildUpdater).Elem().Name() == "errBuildUpdater" {
|
|
| 381 |
+ if err == nil {
|
|
| 382 |
+ t.Errorf("(%d) Expected error, got none", i)
|
|
| 383 |
+ } |
|
| 384 |
+ // can't check tc.outStatus because the local build object does get updated |
|
| 385 |
+ // in this test (but would not updated in etcd) |
|
| 386 |
+ continue |
|
| 387 |
+ } |
|
| 380 | 388 |
if build.Status != tc.outStatus {
|
| 381 | 389 |
t.Errorf("(%d) Expected %s, got %s!", i, tc.outStatus, build.Status)
|
| 382 | 390 |
} |
| ... | ... |
@@ -385,10 +429,12 @@ func TestHandlePod(t *testing.T) {
|
| 385 | 385 |
|
| 386 | 386 |
func TestCancelBuild(t *testing.T) {
|
| 387 | 387 |
type handleCancelBuildTest struct {
|
| 388 |
- inStatus buildapi.BuildStatus |
|
| 389 |
- outStatus buildapi.BuildStatus |
|
| 390 |
- podStatus kapi.PodPhase |
|
| 391 |
- exitCode int |
|
| 388 |
+ inStatus buildapi.BuildStatus |
|
| 389 |
+ outStatus buildapi.BuildStatus |
|
| 390 |
+ podStatus kapi.PodPhase |
|
| 391 |
+ exitCode int |
|
| 392 |
+ buildUpdater buildclient.BuildUpdater |
|
| 393 |
+ podManager podManager |
|
| 392 | 394 |
} |
| 393 | 395 |
|
| 394 | 396 |
tests := []handleCancelBuildTest{
|
| ... | ... |
@@ -421,13 +467,48 @@ func TestCancelBuild(t *testing.T) {
|
| 421 | 421 |
podStatus: kapi.PodFailed, |
| 422 | 422 |
exitCode: 1, |
| 423 | 423 |
}, |
| 424 |
+ { // 5
|
|
| 425 |
+ inStatus: buildapi.BuildStatusNew, |
|
| 426 |
+ outStatus: buildapi.BuildStatusNew, |
|
| 427 |
+ podStatus: kapi.PodFailed, |
|
| 428 |
+ exitCode: 1, |
|
| 429 |
+ podManager: &errPodManager{},
|
|
| 430 |
+ }, |
|
| 431 |
+ { // 6
|
|
| 432 |
+ inStatus: buildapi.BuildStatusNew, |
|
| 433 |
+ outStatus: buildapi.BuildStatusNew, |
|
| 434 |
+ podStatus: kapi.PodFailed, |
|
| 435 |
+ exitCode: 1, |
|
| 436 |
+ buildUpdater: &errBuildUpdater{},
|
|
| 437 |
+ }, |
|
| 424 | 438 |
} |
| 425 | 439 |
|
| 426 | 440 |
for i, tc := range tests {
|
| 427 |
- build, ctrl := mockBuildAndController(tc.inStatus, buildapi.BuildOutput{})
|
|
| 441 |
+ build := mockBuild(tc.inStatus, buildapi.BuildOutput{})
|
|
| 442 |
+ ctrl := mockBuildPodController(build) |
|
| 428 | 443 |
pod := mockPod(tc.podStatus, tc.exitCode) |
| 444 |
+ if tc.buildUpdater != nil {
|
|
| 445 |
+ ctrl.BuildUpdater = tc.buildUpdater |
|
| 446 |
+ } |
|
| 447 |
+ if tc.podManager != nil {
|
|
| 448 |
+ ctrl.PodManager = tc.podManager |
|
| 449 |
+ } |
|
| 429 | 450 |
|
| 430 |
- ctrl.CancelBuild(build, pod) |
|
| 451 |
+ err := ctrl.CancelBuild(build, pod) |
|
| 452 |
+ |
|
| 453 |
+ if tc.podManager != nil && reflect.TypeOf(tc.podManager).Elem().Name() == "errPodManager" {
|
|
| 454 |
+ if err == nil {
|
|
| 455 |
+ t.Errorf("(%d) Expected error, got none", i)
|
|
| 456 |
+ } |
|
| 457 |
+ } |
|
| 458 |
+ if tc.buildUpdater != nil && reflect.TypeOf(tc.buildUpdater).Elem().Name() == "errBuildUpdater" {
|
|
| 459 |
+ if err == nil {
|
|
| 460 |
+ t.Errorf("(%d) Expected error, got none", i)
|
|
| 461 |
+ } |
|
| 462 |
+ // can't check tc.outStatus because the local build object does get updated |
|
| 463 |
+ // in this test (but would not updated in etcd) |
|
| 464 |
+ continue |
|
| 465 |
+ } |
|
| 431 | 466 |
|
| 432 | 467 |
if build.Status != tc.outStatus {
|
| 433 | 468 |
t.Errorf("(%d) Expected %s, got %s!", i, tc.outStatus, build.Status)
|
| ... | ... |
@@ -11,16 +11,19 @@ import ( |
| 11 | 11 |
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache" |
| 12 | 12 |
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" |
| 13 | 13 |
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" |
| 14 |
+ kutil "github.com/GoogleCloudPlatform/kubernetes/pkg/util" |
|
| 14 | 15 |
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch" |
| 15 | 16 |
|
| 16 | 17 |
buildapi "github.com/openshift/origin/pkg/build/api" |
| 17 | 18 |
buildclient "github.com/openshift/origin/pkg/build/client" |
| 18 |
- controller "github.com/openshift/origin/pkg/build/controller" |
|
| 19 |
+ buildcontroller "github.com/openshift/origin/pkg/build/controller" |
|
| 19 | 20 |
strategy "github.com/openshift/origin/pkg/build/controller/strategy" |
| 20 | 21 |
osclient "github.com/openshift/origin/pkg/client" |
| 22 |
+ controller "github.com/openshift/origin/pkg/controller" |
|
| 21 | 23 |
imageapi "github.com/openshift/origin/pkg/image/api" |
| 22 | 24 |
) |
| 23 | 25 |
|
| 26 |
+// BuildControllerFactory constructs BuildController objects |
|
| 24 | 27 |
type BuildControllerFactory struct {
|
| 25 | 28 |
OSClient osclient.Interface |
| 26 | 29 |
KubeClient kclient.Interface |
| ... | ... |
@@ -30,16 +33,55 @@ type BuildControllerFactory struct {
|
| 30 | 30 |
CustomBuildStrategy *strategy.CustomBuildStrategy |
| 31 | 31 |
// Stop may be set to allow controllers created by this factory to be terminated. |
| 32 | 32 |
Stop <-chan struct{}
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+// Create constructs a BuildController |
|
| 36 |
+func (factory *BuildControllerFactory) Create() controller.RunnableController {
|
|
| 37 |
+ queue := cache.NewFIFO(cache.MetaNamespaceKeyFunc) |
|
| 38 |
+ cache.NewReflector(&buildLW{client: factory.OSClient}, &buildapi.Build{}, queue).Run()
|
|
| 39 |
+ |
|
| 40 |
+ client := ControllerClient{factory.KubeClient, factory.OSClient}
|
|
| 41 |
+ buildController := &buildcontroller.BuildController{
|
|
| 42 |
+ BuildUpdater: factory.BuildUpdater, |
|
| 43 |
+ ImageRepositoryClient: client, |
|
| 44 |
+ PodManager: client, |
|
| 45 |
+ BuildStrategy: &typeBasedFactoryStrategy{
|
|
| 46 |
+ DockerBuildStrategy: factory.DockerBuildStrategy, |
|
| 47 |
+ STIBuildStrategy: factory.STIBuildStrategy, |
|
| 48 |
+ CustomBuildStrategy: factory.CustomBuildStrategy, |
|
| 49 |
+ }, |
|
| 50 |
+ } |
|
| 51 |
+ |
|
| 52 |
+ return &controller.RetryController{
|
|
| 53 |
+ Queue: queue, |
|
| 54 |
+ RetryManager: controller.NewQueueRetryManager(queue, cache.MetaNamespaceKeyFunc, -1), |
|
| 55 |
+ ShouldRetry: func(obj interface{}, err error) bool {
|
|
| 56 |
+ kutil.HandleError(err) |
|
| 57 |
+ // BuildController currently has no fatal errors. |
|
| 58 |
+ return true |
|
| 59 |
+ }, |
|
| 60 |
+ Handle: func(obj interface{}) error {
|
|
| 61 |
+ build := obj.(*buildapi.Build) |
|
| 62 |
+ return buildController.HandleBuild(build) |
|
| 63 |
+ }, |
|
| 64 |
+ } |
|
| 65 |
+} |
|
| 66 |
+ |
|
| 67 |
+// BuildPodControllerFactory construct BuildPodController objects |
|
| 68 |
+type BuildPodControllerFactory struct {
|
|
| 69 |
+ OSClient osclient.Interface |
|
| 70 |
+ KubeClient kclient.Interface |
|
| 71 |
+ BuildUpdater buildclient.BuildUpdater |
|
| 72 |
+ // Stop may be set to allow controllers created by this factory to be terminated. |
|
| 73 |
+ Stop <-chan struct{}
|
|
| 33 | 74 |
|
| 34 | 75 |
buildStore cache.Store |
| 35 | 76 |
} |
| 36 | 77 |
|
| 37 |
-func (factory *BuildControllerFactory) Create() *controller.BuildController {
|
|
| 78 |
+// Create constructs a BuildPodController |
|
| 79 |
+func (factory *BuildPodControllerFactory) Create() controller.RunnableController {
|
|
| 38 | 80 |
factory.buildStore = cache.NewStore(cache.MetaNamespaceKeyFunc) |
| 39 |
- cache.NewReflector(&buildLW{client: factory.OSClient}, &buildapi.Build{}, factory.buildStore).RunUntil(factory.Stop)
|
|
| 40 |
- |
|
| 41 |
- buildQueue := cache.NewFIFO(cache.MetaNamespaceKeyFunc) |
|
| 42 |
- cache.NewReflector(&buildLW{client: factory.OSClient}, &buildapi.Build{}, buildQueue).RunUntil(factory.Stop)
|
|
| 81 |
+ cache.NewReflector(&buildLW{client: factory.OSClient}, &buildapi.Build{}, factory.buildStore).Run()
|
|
| 43 | 82 |
|
| 44 | 83 |
// Kubernetes does not currently synchronize Pod status in storage with a Pod's container |
| 45 | 84 |
// states. Because of this, we can't receive events related to container (and thus Pod) |
| ... | ... |
@@ -49,29 +91,27 @@ func (factory *BuildControllerFactory) Create() *controller.BuildController {
|
| 49 | 49 |
// |
| 50 | 50 |
// TODO: Find a way to get watch events for Pod/container status updates. The polling |
| 51 | 51 |
// strategy is horribly inefficient and should be addressed upstream somehow. |
| 52 |
- podQueue := cache.NewFIFO(cache.MetaNamespaceKeyFunc) |
|
| 53 |
- cache.NewPoller(factory.pollPods, 10*time.Second, podQueue).RunUntil(factory.Stop) |
|
| 52 |
+ queue := cache.NewFIFO(cache.MetaNamespaceKeyFunc) |
|
| 53 |
+ cache.NewPoller(factory.pollPods, 10*time.Second, queue).RunUntil(factory.Stop) |
|
| 54 | 54 |
|
| 55 | 55 |
client := ControllerClient{factory.KubeClient, factory.OSClient}
|
| 56 |
- return &controller.BuildController{
|
|
| 57 |
- BuildStore: factory.buildStore, |
|
| 58 |
- BuildUpdater: factory.BuildUpdater, |
|
| 59 |
- ImageRepositoryClient: client, |
|
| 60 |
- PodManager: client, |
|
| 61 |
- NextBuild: func() *buildapi.Build {
|
|
| 62 |
- build := buildQueue.Pop().(*buildapi.Build) |
|
| 63 |
- panicIfStopped(factory.Stop, "build controller stopped") |
|
| 64 |
- return build |
|
| 65 |
- }, |
|
| 66 |
- NextPod: func() *kapi.Pod {
|
|
| 67 |
- pod := podQueue.Pop().(*kapi.Pod) |
|
| 68 |
- panicIfStopped(factory.Stop, "build controller stopped") |
|
| 69 |
- return pod |
|
| 56 |
+ buildPodController := &buildcontroller.BuildPodController{
|
|
| 57 |
+ BuildStore: factory.buildStore, |
|
| 58 |
+ BuildUpdater: factory.BuildUpdater, |
|
| 59 |
+ PodManager: client, |
|
| 60 |
+ } |
|
| 61 |
+ |
|
| 62 |
+ return &controller.RetryController{
|
|
| 63 |
+ Queue: queue, |
|
| 64 |
+ RetryManager: controller.NewQueueRetryManager(queue, cache.MetaNamespaceKeyFunc, -1), |
|
| 65 |
+ ShouldRetry: func(obj interface{}, err error) bool {
|
|
| 66 |
+ kutil.HandleError(err) |
|
| 67 |
+ // BuildPodController currently has no fatal errors. |
|
| 68 |
+ return true |
|
| 70 | 69 |
}, |
| 71 |
- BuildStrategy: &typeBasedFactoryStrategy{
|
|
| 72 |
- DockerBuildStrategy: factory.DockerBuildStrategy, |
|
| 73 |
- STIBuildStrategy: factory.STIBuildStrategy, |
|
| 74 |
- CustomBuildStrategy: factory.CustomBuildStrategy, |
|
| 70 |
+ Handle: func(obj interface{}) error {
|
|
| 71 |
+ pod := obj.(*kapi.Pod) |
|
| 72 |
+ return buildPodController.HandlePod(pod) |
|
| 75 | 73 |
}, |
| 76 | 74 |
} |
| 77 | 75 |
} |
| ... | ... |
@@ -88,29 +128,40 @@ type ImageChangeControllerFactory struct {
|
| 88 | 88 |
|
| 89 | 89 |
// Create creates a new ImageChangeController which is used to trigger builds when a new |
| 90 | 90 |
// image is available |
| 91 |
-func (factory *ImageChangeControllerFactory) Create() *controller.ImageChangeController {
|
|
| 91 |
+func (factory *ImageChangeControllerFactory) Create() controller.RunnableController {
|
|
| 92 | 92 |
queue := cache.NewFIFO(cache.MetaNamespaceKeyFunc) |
| 93 |
- cache.NewReflector(&imageRepositoryLW{factory.Client}, &imageapi.ImageRepository{}, queue).RunUntil(factory.Stop)
|
|
| 93 |
+ cache.NewReflector(&imageRepositoryLW{factory.Client}, &imageapi.ImageRepository{}, queue).Run()
|
|
| 94 | 94 |
|
| 95 | 95 |
store := cache.NewStore(cache.MetaNamespaceKeyFunc) |
| 96 |
- cache.NewReflector(&buildConfigLW{client: factory.Client}, &buildapi.BuildConfig{}, store).RunUntil(factory.Stop)
|
|
| 96 |
+ cache.NewReflector(&buildConfigLW{client: factory.Client}, &buildapi.BuildConfig{}, store).Run()
|
|
| 97 | 97 |
|
| 98 |
- return &controller.ImageChangeController{
|
|
| 98 |
+ imageChangeController := &buildcontroller.ImageChangeController{
|
|
| 99 | 99 |
BuildConfigStore: store, |
| 100 | 100 |
BuildConfigUpdater: factory.BuildConfigUpdater, |
| 101 | 101 |
BuildCreator: factory.BuildCreator, |
| 102 |
- NextImageRepository: func() *imageapi.ImageRepository {
|
|
| 103 |
- repo := queue.Pop().(*imageapi.ImageRepository) |
|
| 104 |
- panicIfStopped(factory.Stop, "build image change controller stopped") |
|
| 105 |
- return repo |
|
| 102 |
+ Stop: factory.Stop, |
|
| 103 |
+ } |
|
| 104 |
+ |
|
| 105 |
+ return &controller.RetryController{
|
|
| 106 |
+ Queue: queue, |
|
| 107 |
+ RetryManager: controller.NewQueueRetryManager(queue, cache.MetaNamespaceKeyFunc, -1), |
|
| 108 |
+ ShouldRetry: func(obj interface{}, err error) bool {
|
|
| 109 |
+ kutil.HandleError(err) |
|
| 110 |
+ if _, isFatal := err.(buildcontroller.ImageChangeControllerFatalError); isFatal {
|
|
| 111 |
+ return false |
|
| 112 |
+ } |
|
| 113 |
+ return true |
|
| 114 |
+ }, |
|
| 115 |
+ Handle: func(obj interface{}) error {
|
|
| 116 |
+ imageRepo := obj.(*imageapi.ImageRepository) |
|
| 117 |
+ return imageChangeController.HandleImageRepo(imageRepo) |
|
| 106 | 118 |
}, |
| 107 |
- Stop: factory.Stop, |
|
| 108 | 119 |
} |
| 109 | 120 |
} |
| 110 | 121 |
|
| 111 | 122 |
// pollPods lists pods for all builds in the buildStore which are pending or running and |
| 112 | 123 |
// returns an enumerator for cache.Poller. The poll scope is narrowed for efficiency. |
| 113 |
-func (factory *BuildControllerFactory) pollPods() (cache.Enumerator, error) {
|
|
| 124 |
+func (factory *BuildPodControllerFactory) pollPods() (cache.Enumerator, error) {
|
|
| 114 | 125 |
list := &kapi.PodList{}
|
| 115 | 126 |
|
| 116 | 127 |
for _, obj := range factory.buildStore.List() {
|
| ... | ... |
@@ -1,10 +1,10 @@ |
| 1 | 1 |
package controller |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "github.com/golang/glog" |
|
| 4 |
+ "fmt" |
|
| 5 | 5 |
|
| 6 | 6 |
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache" |
| 7 |
- "github.com/GoogleCloudPlatform/kubernetes/pkg/util" |
|
| 7 |
+ "github.com/golang/glog" |
|
| 8 | 8 |
|
| 9 | 9 |
buildapi "github.com/openshift/origin/pkg/build/api" |
| 10 | 10 |
buildclient "github.com/openshift/origin/pkg/build/client" |
| ... | ... |
@@ -12,27 +12,28 @@ import ( |
| 12 | 12 |
imageapi "github.com/openshift/origin/pkg/image/api" |
| 13 | 13 |
) |
| 14 | 14 |
|
| 15 |
+type ImageChangeControllerFatalError struct {
|
|
| 16 |
+ Reason string |
|
| 17 |
+ Err error |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+func (e ImageChangeControllerFatalError) Error() string {
|
|
| 21 |
+ return "fatal error handling ImageRepository change: " + e.Reason |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 15 | 24 |
// ImageChangeController watches for changes to ImageRepositories and triggers |
| 16 | 25 |
// builds when a new version of a tag referenced by a BuildConfig |
| 17 | 26 |
// is available. |
| 18 | 27 |
type ImageChangeController struct {
|
| 19 |
- NextImageRepository func() *imageapi.ImageRepository |
|
| 20 |
- BuildConfigStore cache.Store |
|
| 21 |
- BuildCreator buildclient.BuildCreator |
|
| 22 |
- BuildConfigUpdater buildclient.BuildConfigUpdater |
|
| 28 |
+ BuildConfigStore cache.Store |
|
| 29 |
+ BuildCreator buildclient.BuildCreator |
|
| 30 |
+ BuildConfigUpdater buildclient.BuildConfigUpdater |
|
| 23 | 31 |
// Stop is an optional channel that controls when the controller exits |
| 24 | 32 |
Stop <-chan struct{}
|
| 25 | 33 |
} |
| 26 | 34 |
|
| 27 |
-// Run processes ImageRepository events one by one. |
|
| 28 |
-func (c *ImageChangeController) Run() {
|
|
| 29 |
- go util.Until(c.HandleImageRepo, 0, c.Stop) |
|
| 30 |
-} |
|
| 31 |
- |
|
| 32 | 35 |
// HandleImageRepo processes the next ImageRepository event. |
| 33 |
-func (c *ImageChangeController) HandleImageRepo() {
|
|
| 34 |
- glog.V(4).Infof("Waiting for imagerepo change")
|
|
| 35 |
- imageRepo := c.NextImageRepository() |
|
| 36 |
+func (c *ImageChangeController) HandleImageRepo(imageRepo *imageapi.ImageRepository) error {
|
|
| 36 | 37 |
glog.V(4).Infof("Build image change controller detected imagerepo change %s", imageRepo.DockerImageRepository)
|
| 37 | 38 |
imageSubstitutions := make(map[string]string) |
| 38 | 39 |
|
| ... | ... |
@@ -78,12 +79,18 @@ func (c *ImageChangeController) HandleImageRepo() {
|
| 78 | 78 |
glog.V(4).Infof("Running build for buildConfig %s in namespace %s", config.Name, config.Namespace)
|
| 79 | 79 |
b := buildutil.GenerateBuildFromConfig(config, nil, imageSubstitutions) |
| 80 | 80 |
if err := c.BuildCreator.Create(config.Namespace, b); err != nil {
|
| 81 |
- glog.V(2).Infof("Error starting build for buildConfig %v: %v", config.Name, err)
|
|
| 81 |
+ return fmt.Errorf("Error starting build for buildConfig %s: %v", config.Name, err)
|
|
| 82 | 82 |
} else {
|
| 83 | 83 |
if err := c.BuildConfigUpdater.Update(config); err != nil {
|
| 84 |
+ // This is not a retryable error because the build has been created. The worst case |
|
| 85 |
+ // outcome of not updating the buildconfig is that we might rerun a build for the |
|
| 86 |
+ // same "new" imageid change in the future, which is better than guaranteeing we |
|
| 87 |
+ // run the build 2+ times by retrying it here. |
|
| 84 | 88 |
glog.V(2).Infof("Error updating buildConfig %v: %v", config.Name, err)
|
| 89 |
+ return ImageChangeControllerFatalError{Reason: fmt.Sprintf("Error updating buildConfig %s with new LastTriggeredImageID", config.Name), Err: err}
|
|
| 85 | 90 |
} |
| 86 | 91 |
} |
| 87 | 92 |
} |
| 88 | 93 |
} |
| 94 |
+ return nil |
|
| 89 | 95 |
} |
| ... | ... |
@@ -13,11 +13,12 @@ import ( |
| 13 | 13 |
|
| 14 | 14 |
type mockBuildConfigUpdater struct {
|
| 15 | 15 |
buildcfg *buildapi.BuildConfig |
| 16 |
+ err error |
|
| 16 | 17 |
} |
| 17 | 18 |
|
| 18 | 19 |
func (m *mockBuildConfigUpdater) Update(buildcfg *buildapi.BuildConfig) error {
|
| 19 | 20 |
m.buildcfg = buildcfg |
| 20 |
- return nil |
|
| 21 |
+ return m.err |
|
| 21 | 22 |
} |
| 22 | 23 |
|
| 23 | 24 |
type mockBuildCreator struct {
|
| ... | ... |
@@ -71,8 +72,8 @@ func appendTrigger(buildcfg *buildapi.BuildConfig, triggerImage, repoName, repoT |
| 71 | 71 |
}) |
| 72 | 72 |
} |
| 73 | 73 |
|
| 74 |
-func mockImageChangeController(buildcfg *buildapi.BuildConfig, repoName, dockerImageRepo string, tags map[string]string) *ImageChangeController {
|
|
| 75 |
- imageRepo := imageapi.ImageRepository{
|
|
| 74 |
+func mockImageRepo(repoName, dockerImageRepo string, tags map[string]string) *imageapi.ImageRepository {
|
|
| 75 |
+ return &imageapi.ImageRepository{
|
|
| 76 | 76 |
ObjectMeta: kapi.ObjectMeta{
|
| 77 | 77 |
Name: repoName, |
| 78 | 78 |
}, |
| ... | ... |
@@ -81,23 +82,28 @@ func mockImageChangeController(buildcfg *buildapi.BuildConfig, repoName, dockerI |
| 81 | 81 |
}, |
| 82 | 82 |
Tags: tags, |
| 83 | 83 |
} |
| 84 |
+} |
|
| 84 | 85 |
|
| 86 |
+func mockImageChangeController(buildcfg *buildapi.BuildConfig) *ImageChangeController {
|
|
| 85 | 87 |
return &ImageChangeController{
|
| 86 |
- NextImageRepository: func() *imageapi.ImageRepository { return &imageRepo },
|
|
| 87 |
- BuildConfigStore: buildtest.NewFakeBuildConfigStore(buildcfg), |
|
| 88 |
- BuildCreator: &mockBuildCreator{},
|
|
| 89 |
- BuildConfigUpdater: &mockBuildConfigUpdater{},
|
|
| 88 |
+ BuildConfigStore: buildtest.NewFakeBuildConfigStore(buildcfg), |
|
| 89 |
+ BuildCreator: &mockBuildCreator{},
|
|
| 90 |
+ BuildConfigUpdater: &mockBuildConfigUpdater{},
|
|
| 90 | 91 |
} |
| 91 | 92 |
} |
| 92 | 93 |
|
| 93 | 94 |
func TestNewImageID(t *testing.T) {
|
| 94 | 95 |
// valid configuration, new build should be triggered. |
| 95 | 96 |
buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageRepo", "testTag")
|
| 96 |
- controller := mockImageChangeController(buildcfg, "testImageRepo", "registry.com/namespace/imagename", map[string]string{"testTag": "newImageID123"})
|
|
| 97 |
- controller.HandleImageRepo() |
|
| 97 |
+ imagerepo := mockImageRepo("testImageRepo", "registry.com/namespace/imagename", map[string]string{"testTag": "newImageID123"})
|
|
| 98 |
+ controller := mockImageChangeController(buildcfg) |
|
| 99 |
+ err := controller.HandleImageRepo(imagerepo) |
|
| 98 | 100 |
buildCreator := controller.BuildCreator.(*mockBuildCreator) |
| 99 | 101 |
buildConfigUpdater := controller.BuildConfigUpdater.(*mockBuildConfigUpdater) |
| 100 | 102 |
|
| 103 |
+ if err != nil {
|
|
| 104 |
+ t.Errorf("Unexpected error %v from HandleImageRepo", err)
|
|
| 105 |
+ } |
|
| 101 | 106 |
if buildCreator.build == nil {
|
| 102 | 107 |
t.Error("Expected new build when new image was created!")
|
| 103 | 108 |
} |
| ... | ... |
@@ -115,11 +121,15 @@ func TestNewImageID(t *testing.T) {
|
| 115 | 115 |
func TestNewImageIDDefaultTag(t *testing.T) {
|
| 116 | 116 |
// valid configuration using default tag, new build should be triggered. |
| 117 | 117 |
buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageRepo", "")
|
| 118 |
- controller := mockImageChangeController(buildcfg, "testImageRepo", "registry.com/namespace/imagename", map[string]string{buildapi.DefaultImageTag: "newImageID123"})
|
|
| 119 |
- controller.HandleImageRepo() |
|
| 118 |
+ imagerepo := mockImageRepo("testImageRepo", "registry.com/namespace/imagename", map[string]string{buildapi.DefaultImageTag: "newImageID123"})
|
|
| 119 |
+ controller := mockImageChangeController(buildcfg) |
|
| 120 |
+ err := controller.HandleImageRepo(imagerepo) |
|
| 120 | 121 |
buildCreator := controller.BuildCreator.(*mockBuildCreator) |
| 121 | 122 |
buildConfigUpdater := controller.BuildConfigUpdater.(*mockBuildConfigUpdater) |
| 122 | 123 |
|
| 124 |
+ if err != nil {
|
|
| 125 |
+ t.Errorf("Unexpected error %v from HandleImageRepo", err)
|
|
| 126 |
+ } |
|
| 123 | 127 |
if buildCreator.build == nil {
|
| 124 | 128 |
t.Error("Expected new build when new image was created!")
|
| 125 | 129 |
} |
| ... | ... |
@@ -138,11 +148,15 @@ func TestNonExistentImageRepository(t *testing.T) {
|
| 138 | 138 |
// this buildconfig references a non-existent imagerepo, so an update to the real imagerepo should not |
| 139 | 139 |
// trigger a build here. |
| 140 | 140 |
buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageRepo", "testTag")
|
| 141 |
- controller := mockImageChangeController(buildcfg, "otherImageRepo", "registry.com/namespace/imagename", map[string]string{"testTag": "newImageID123"})
|
|
| 142 |
- controller.HandleImageRepo() |
|
| 141 |
+ imagerepo := mockImageRepo("otherImageRepo", "registry.com/namespace/imagename", map[string]string{"testTag": "newImageID123"})
|
|
| 142 |
+ controller := mockImageChangeController(buildcfg) |
|
| 143 |
+ err := controller.HandleImageRepo(imagerepo) |
|
| 143 | 144 |
buildCreator := controller.BuildCreator.(*mockBuildCreator) |
| 144 | 145 |
buildConfigUpdater := controller.BuildConfigUpdater.(*mockBuildConfigUpdater) |
| 145 | 146 |
|
| 147 |
+ if err != nil {
|
|
| 148 |
+ t.Errorf("Unexpected error %v from HandleImageRepo", err)
|
|
| 149 |
+ } |
|
| 146 | 150 |
if buildCreator.build != nil {
|
| 147 | 151 |
t.Error("New build created when a different repository was updated!")
|
| 148 | 152 |
} |
| ... | ... |
@@ -154,11 +168,15 @@ func TestNonExistentImageRepository(t *testing.T) {
|
| 154 | 154 |
func TestNewImageDifferentTagUpdate(t *testing.T) {
|
| 155 | 155 |
// this buildconfig references a different tag than the one that will be updated |
| 156 | 156 |
buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageRepo", "testTag")
|
| 157 |
- controller := mockImageChangeController(buildcfg, "testImageRepo", "registry.com/namespace/imagename", map[string]string{"otherTag": "newImageID123"})
|
|
| 158 |
- controller.HandleImageRepo() |
|
| 157 |
+ imagerepo := mockImageRepo("testImageRepo", "registry.com/namespace/imagename", map[string]string{"otherTag": "newImageID123"})
|
|
| 158 |
+ controller := mockImageChangeController(buildcfg) |
|
| 159 |
+ err := controller.HandleImageRepo(imagerepo) |
|
| 159 | 160 |
buildCreator := controller.BuildCreator.(*mockBuildCreator) |
| 160 | 161 |
buildConfigUpdater := controller.BuildConfigUpdater.(*mockBuildConfigUpdater) |
| 161 | 162 |
|
| 163 |
+ if err != nil {
|
|
| 164 |
+ t.Errorf("Unexpected error %v from HandleImageRepo", err)
|
|
| 165 |
+ } |
|
| 162 | 166 |
if buildCreator.build != nil {
|
| 163 | 167 |
t.Error("New build created when a different repository was updated!")
|
| 164 | 168 |
} |
| ... | ... |
@@ -172,12 +190,15 @@ func TestNewImageDifferentTagUpdate2(t *testing.T) {
|
| 172 | 172 |
// it has previously run a build for the testTagID123 tag. |
| 173 | 173 |
buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageRepo", "testTag")
|
| 174 | 174 |
buildcfg.Triggers[0].ImageChange.LastTriggeredImageID = "testTagID123" |
| 175 |
- controller := mockImageChangeController(buildcfg, "testImageRepo", "registry.com/namespace/imagename", |
|
| 176 |
- map[string]string{"otherTag": "newImageID123", "testTag": "testTagID123"})
|
|
| 177 |
- controller.HandleImageRepo() |
|
| 175 |
+ imagerepo := mockImageRepo("testImageRepo", "registry.com/namespace/imagename", map[string]string{"otherTag": "newImageID123", "testTag": "testTagID123"})
|
|
| 176 |
+ controller := mockImageChangeController(buildcfg) |
|
| 177 |
+ err := controller.HandleImageRepo(imagerepo) |
|
| 178 | 178 |
buildCreator := controller.BuildCreator.(*mockBuildCreator) |
| 179 | 179 |
buildConfigUpdater := controller.BuildConfigUpdater.(*mockBuildConfigUpdater) |
| 180 | 180 |
|
| 181 |
+ if err != nil {
|
|
| 182 |
+ t.Errorf("Unexpected error %v from HandleImageRepo", err)
|
|
| 183 |
+ } |
|
| 181 | 184 |
if buildCreator.build != nil {
|
| 182 | 185 |
t.Error("New build created when a different repository was updated!")
|
| 183 | 186 |
} |
| ... | ... |
@@ -189,11 +210,15 @@ func TestNewImageDifferentTagUpdate2(t *testing.T) {
|
| 189 | 189 |
func TestNewDifferentImageUpdate(t *testing.T) {
|
| 190 | 190 |
// this buildconfig references a different image than the one that will be updated |
| 191 | 191 |
buildcfg := mockBuildConfig("registry.com/namespace/imagename1", "registry.com/namespace/imagename1", "testImageRepo1", "testTag1")
|
| 192 |
- controller := mockImageChangeController(buildcfg, "testImageRepo2", "registry.com/namespace/imagename2", map[string]string{"testTag2": "newImageID123"})
|
|
| 193 |
- controller.HandleImageRepo() |
|
| 192 |
+ imagerepo := mockImageRepo("testImageRepo2", "registry.com/namespace/imagename2", map[string]string{"testTag2": "newImageID123"})
|
|
| 193 |
+ controller := mockImageChangeController(buildcfg) |
|
| 194 |
+ err := controller.HandleImageRepo(imagerepo) |
|
| 194 | 195 |
buildCreator := controller.BuildCreator.(*mockBuildCreator) |
| 195 | 196 |
buildConfigUpdater := controller.BuildConfigUpdater.(*mockBuildConfigUpdater) |
| 196 | 197 |
|
| 198 |
+ if err != nil {
|
|
| 199 |
+ t.Errorf("Unexpected error %v from HandleImageRepo", err)
|
|
| 200 |
+ } |
|
| 197 | 201 |
if buildCreator.build != nil {
|
| 198 | 202 |
t.Error("New build created when a different repository was updated!")
|
| 199 | 203 |
} |
| ... | ... |
@@ -206,11 +231,15 @@ func TestMultipleTriggers(t *testing.T) {
|
| 206 | 206 |
// this buildconfig references multiple images |
| 207 | 207 |
buildcfg := mockBuildConfig("registry.com/namespace/imagename1", "registry.com/namespace/imagename1", "testImageRepo1", "testTag1")
|
| 208 | 208 |
appendTrigger(buildcfg, "registry.com/namespace/imagename2", "testImageRepo2", "testTag2") |
| 209 |
- controller := mockImageChangeController(buildcfg, "testImageRepo2", "registry.com/namespace/imagename2", map[string]string{"testTag2": "newImageID123"})
|
|
| 210 |
- controller.HandleImageRepo() |
|
| 209 |
+ imagerepo := mockImageRepo("testImageRepo2", "registry.com/namespace/imagename2", map[string]string{"testTag2": "newImageID123"})
|
|
| 210 |
+ controller := mockImageChangeController(buildcfg) |
|
| 211 |
+ err := controller.HandleImageRepo(imagerepo) |
|
| 211 | 212 |
buildCreator := controller.BuildCreator.(*mockBuildCreator) |
| 212 | 213 |
buildConfigUpdater := controller.BuildConfigUpdater.(*mockBuildConfigUpdater) |
| 213 | 214 |
|
| 215 |
+ if err != nil {
|
|
| 216 |
+ t.Errorf("Unexpected error %v from HandleImageRepo", err)
|
|
| 217 |
+ } |
|
| 214 | 218 |
if buildCreator.build == nil {
|
| 215 | 219 |
t.Error("Expected new build when new image was created!")
|
| 216 | 220 |
} |
| ... | ... |
@@ -230,11 +259,15 @@ func TestBuildConfigWithDifferentTriggerType(t *testing.T) {
|
| 230 | 230 |
// this buildconfig has different (than ImageChangeTrigger) trigger defined |
| 231 | 231 |
buildcfg := mockBuildConfig("registry.com/namespace/imagename1", "", "", "")
|
| 232 | 232 |
buildcfg.Triggers[0].Type = buildapi.GenericWebHookBuildTriggerType |
| 233 |
- controller := mockImageChangeController(buildcfg, "testImageRepo2", "registry.com/namespace/imagename2", map[string]string{"testTag2": "newImageID123"})
|
|
| 234 |
- controller.HandleImageRepo() |
|
| 233 |
+ imagerepo := mockImageRepo("testImageRepo2", "registry.com/namespace/imagename2", map[string]string{"testTag2": "newImageID123"})
|
|
| 234 |
+ controller := mockImageChangeController(buildcfg) |
|
| 235 |
+ err := controller.HandleImageRepo(imagerepo) |
|
| 235 | 236 |
buildCreator := controller.BuildCreator.(*mockBuildCreator) |
| 236 | 237 |
buildConfigUpdater := controller.BuildConfigUpdater.(*mockBuildConfigUpdater) |
| 237 | 238 |
|
| 239 |
+ if err != nil {
|
|
| 240 |
+ t.Errorf("Unexpected error %v from HandleImageRepo", err)
|
|
| 241 |
+ } |
|
| 238 | 242 |
if buildCreator.build != nil {
|
| 239 | 243 |
t.Error("New build created when a different trigger type was defined!")
|
| 240 | 244 |
} |
| ... | ... |
@@ -248,11 +281,15 @@ func TestNoImageIDChange(t *testing.T) {
|
| 248 | 248 |
// startup when we're checking all the imageRepos |
| 249 | 249 |
buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageRepo", "testTag")
|
| 250 | 250 |
buildcfg.Triggers[0].ImageChange.LastTriggeredImageID = "imageID123" |
| 251 |
- controller := mockImageChangeController(buildcfg, "testImageRepo", "registry.com/namespace/imagename", map[string]string{"testTag": "imageID123"})
|
|
| 252 |
- controller.HandleImageRepo() |
|
| 251 |
+ imagerepo := mockImageRepo("testImageRepo", "registry.com/namespace/imagename", map[string]string{"testTag": "imageID123"})
|
|
| 252 |
+ controller := mockImageChangeController(buildcfg) |
|
| 253 |
+ err := controller.HandleImageRepo(imagerepo) |
|
| 253 | 254 |
buildCreator := controller.BuildCreator.(*mockBuildCreator) |
| 254 | 255 |
buildConfigUpdater := controller.BuildConfigUpdater.(*mockBuildConfigUpdater) |
| 255 | 256 |
|
| 257 |
+ if err != nil {
|
|
| 258 |
+ t.Errorf("Unexpected error %v from HandleImageRepo", err)
|
|
| 259 |
+ } |
|
| 256 | 260 |
if buildCreator.build != nil {
|
| 257 | 261 |
t.Error("New build created when no change happened!")
|
| 258 | 262 |
} |
| ... | ... |
@@ -264,12 +301,19 @@ func TestNoImageIDChange(t *testing.T) {
|
| 264 | 264 |
func TestBuildCreateError(t *testing.T) {
|
| 265 | 265 |
// valid configuration, but build creation fails, in that situation the buildconfig should not be updated |
| 266 | 266 |
buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageRepo", "testTag")
|
| 267 |
- controller := mockImageChangeController(buildcfg, "testImageRepo", "registry.com/namespace/imagename", map[string]string{"testTag": "newImageID123"})
|
|
| 267 |
+ imagerepo := mockImageRepo("testImageRepo", "registry.com/namespace/imagename", map[string]string{"testTag": "newImageID123"})
|
|
| 268 |
+ controller := mockImageChangeController(buildcfg) |
|
| 268 | 269 |
buildCreator := controller.BuildCreator.(*mockBuildCreator) |
| 269 | 270 |
buildCreator.err = fmt.Errorf("error")
|
| 270 |
- controller.HandleImageRepo() |
|
| 271 |
+ err := controller.HandleImageRepo(imagerepo) |
|
| 271 | 272 |
buildConfigUpdater := controller.BuildConfigUpdater.(*mockBuildConfigUpdater) |
| 272 | 273 |
|
| 274 |
+ if err == nil {
|
|
| 275 |
+ t.Error("Expected error from HandleImageRepo")
|
|
| 276 |
+ } |
|
| 277 |
+ if _, ok := err.(ImageChangeControllerFatalError); ok {
|
|
| 278 |
+ t.Error("Expected retryable error from HandleImageRepo")
|
|
| 279 |
+ } |
|
| 273 | 280 |
if buildCreator.build == nil {
|
| 274 | 281 |
t.Error("Expected new build when new image was created!")
|
| 275 | 282 |
} |
| ... | ... |
@@ -281,14 +325,39 @@ func TestBuildCreateError(t *testing.T) {
|
| 281 | 281 |
} |
| 282 | 282 |
} |
| 283 | 283 |
|
| 284 |
+func TestBuildUpdateError(t *testing.T) {
|
|
| 285 |
+ // valid configuration, but build creation fails, in that situation the buildconfig should not be updated |
|
| 286 |
+ buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageRepo", "testTag")
|
|
| 287 |
+ imagerepo := mockImageRepo("testImageRepo", "registry.com/namespace/imagename", map[string]string{"testTag": "newImageID123"})
|
|
| 288 |
+ controller := mockImageChangeController(buildcfg) |
|
| 289 |
+ buildCreator := controller.BuildCreator.(*mockBuildCreator) |
|
| 290 |
+ buildConfigUpdater := controller.BuildConfigUpdater.(*mockBuildConfigUpdater) |
|
| 291 |
+ buildConfigUpdater.err = fmt.Errorf("error")
|
|
| 292 |
+ err := controller.HandleImageRepo(imagerepo) |
|
| 293 |
+ |
|
| 294 |
+ if _, ok := err.(ImageChangeControllerFatalError); !ok {
|
|
| 295 |
+ t.Error("Expected fatal error from HandleImageRepo")
|
|
| 296 |
+ } |
|
| 297 |
+ if buildCreator.build == nil {
|
|
| 298 |
+ t.Error("Expected new build when new image was created!")
|
|
| 299 |
+ } |
|
| 300 |
+ if buildCreator.build.Parameters.Strategy.DockerStrategy.Image != "registry.com/namespace/imagename:newImageID123" {
|
|
| 301 |
+ t.Errorf("Image substitutions not properly setup for new build. Expected %s, got %s |", "registry.com/namespace/imagename:newImageID123", buildCreator.build.Parameters.Strategy.DockerStrategy.Image)
|
|
| 302 |
+ } |
|
| 303 |
+} |
|
| 304 |
+ |
|
| 284 | 305 |
func TestNewImageIDNoDockerRepo(t *testing.T) {
|
| 285 | 306 |
// No docker repository associated with the imagerepo, so no build can be created |
| 286 | 307 |
buildcfg := mockBuildConfig("registry.com/namespace/imagename", "registry.com/namespace/imagename", "testImageRepo", "testTag")
|
| 287 |
- controller := mockImageChangeController(buildcfg, "testImageRepo", "", map[string]string{"testTag": "newImageID123"})
|
|
| 288 |
- controller.HandleImageRepo() |
|
| 308 |
+ imagerepo := mockImageRepo("testImageRepo", "", map[string]string{"testTag": "newImageID123"})
|
|
| 309 |
+ controller := mockImageChangeController(buildcfg) |
|
| 310 |
+ err := controller.HandleImageRepo(imagerepo) |
|
| 289 | 311 |
buildCreator := controller.BuildCreator.(*mockBuildCreator) |
| 290 | 312 |
buildConfigUpdater := controller.BuildConfigUpdater.(*mockBuildConfigUpdater) |
| 291 | 313 |
|
| 314 |
+ if err != nil {
|
|
| 315 |
+ t.Errorf("Unexpected error %v from HandleImageRepo", err)
|
|
| 316 |
+ } |
|
| 292 | 317 |
if buildCreator.build != nil {
|
| 293 | 318 |
t.Error("New build created when no change happened!")
|
| 294 | 319 |
} |
| ... | ... |
@@ -596,6 +596,18 @@ func (c *MasterConfig) RunBuildController() {
|
| 596 | 596 |
controller.Run() |
| 597 | 597 |
} |
| 598 | 598 |
|
| 599 |
+// RunBuildPodController starts the build/pod status sync loop for build status |
|
| 600 |
+func (c *MasterConfig) RunBuildPodController() {
|
|
| 601 |
+ osclient, kclient := c.BuildControllerClients() |
|
| 602 |
+ factory := buildcontrollerfactory.BuildPodControllerFactory{
|
|
| 603 |
+ OSClient: osclient, |
|
| 604 |
+ KubeClient: kclient, |
|
| 605 |
+ BuildUpdater: buildclient.NewOSClientBuildClient(osclient), |
|
| 606 |
+ } |
|
| 607 |
+ controller := factory.Create() |
|
| 608 |
+ controller.Run() |
|
| 609 |
+} |
|
| 610 |
+ |
|
| 599 | 611 |
// RunDeploymentController starts the build image change trigger controller process. |
| 600 | 612 |
func (c *MasterConfig) RunBuildImageChangeTriggerController() {
|
| 601 | 613 |
bcClient, _ := c.BuildControllerClients() |
| ... | ... |
@@ -98,6 +98,7 @@ func (cfg Config) startMaster() error {
|
| 98 | 98 |
|
| 99 | 99 |
openshiftConfig.RunAssetServer() |
| 100 | 100 |
openshiftConfig.RunBuildController() |
| 101 |
+ openshiftConfig.RunBuildPodController() |
|
| 101 | 102 |
openshiftConfig.RunBuildImageChangeTriggerController() |
| 102 | 103 |
openshiftConfig.RunDeploymentController() |
| 103 | 104 |
openshiftConfig.RunDeployerPodController() |
| ... | ... |
@@ -203,7 +203,7 @@ func NewTestBuildOpenshift(t *testing.T) *testBuildOpenshift {
|
| 203 | 203 |
"github": github.New(), |
| 204 | 204 |
}))) |
| 205 | 205 |
|
| 206 |
- factory := buildcontrollerfactory.BuildControllerFactory{
|
|
| 206 |
+ bcFactory := buildcontrollerfactory.BuildControllerFactory{
|
|
| 207 | 207 |
OSClient: osClient, |
| 208 | 208 |
KubeClient: kubeClient, |
| 209 | 209 |
BuildUpdater: buildclient.NewOSClientBuildClient(osClient), |
| ... | ... |
@@ -219,7 +219,16 @@ func NewTestBuildOpenshift(t *testing.T) *testBuildOpenshift {
|
| 219 | 219 |
Stop: openshift.stop, |
| 220 | 220 |
} |
| 221 | 221 |
|
| 222 |
- factory.Create().Run() |
|
| 222 |
+ bcFactory.Create().Run() |
|
| 223 |
+ |
|
| 224 |
+ bpcFactory := buildcontrollerfactory.BuildPodControllerFactory{
|
|
| 225 |
+ OSClient: osClient, |
|
| 226 |
+ KubeClient: kubeClient, |
|
| 227 |
+ BuildUpdater: buildclient.NewOSClientBuildClient(osClient), |
|
| 228 |
+ Stop: openshift.stop, |
|
| 229 |
+ } |
|
| 230 |
+ |
|
| 231 |
+ bpcFactory.Create().Run() |
|
| 223 | 232 |
|
| 224 | 233 |
return openshift |
| 225 | 234 |
} |