diff --git a/pkg/build/api/types.go b/pkg/build/api/types.go index ef7bac43ab77..94afec85abc2 100644 --- a/pkg/build/api/types.go +++ b/pkg/build/api/types.go @@ -63,6 +63,23 @@ const ( // forces the build to be processed by the build controller queue without waiting // for a resync. BuildAcceptedAnnotation = "build.openshift.io/accepted" + + // BuildStartedEventReason is the reason associated with the event registered when a build is started (pod is created). + BuildStartedEventReason = "BuildStarted" + // BuildStartedEventMessage is the message associated with the event registered when a build is started (pod is created). + BuildStartedEventMessage = "Build %s/%s is now running" + // BuildCompletedEventReason is the reason associated with the event registered when build completes successfully. + BuildCompletedEventReason = "BuildCompleted" + // BuildCompletedEventMessage is the message associated with the event registered when build completes successfully. + BuildCompletedEventMessage = "Build %s/%s completed successfully" + // BuildFailedEventReason is the reason associated with the event registered when build fails. + BuildFailedEventReason = "BuildFailed" + // BuildFailedEventMessage is the message associated with the event registered when build fails. + BuildFailedEventMessage = "Build %s/%s failed" + // BuildCancelledEventReason is the reason associated with the event registered when build is cancelled. + BuildCancelledEventReason = "BuildCancelled" + // BuildCancelledEventMessage is the message associated with the event registered when build is cancelled. + BuildCancelledEventMessage = "Build %s/%s has been cancelled" ) // +genclient=true diff --git a/pkg/build/client/clients.go b/pkg/build/client/clients.go index 00d70144de86..ca07bee6541d 100644 --- a/pkg/build/client/clients.go +++ b/pkg/build/client/clients.go @@ -47,7 +47,7 @@ type BuildLister interface { List(namespace string, opts kapi.ListOptions) (*buildapi.BuildList, error) } -// OSClientBuildClient deletes build create and update operations to the OpenShift client interface +// OSClientBuildClient delegates build create and update operations to the OpenShift client interface type OSClientBuildClient struct { Client osclient.Interface } diff --git a/pkg/build/controller/buildpod/controller.go b/pkg/build/controller/buildpod/controller.go index 1e5eb7d7dd96..a5fa08922ce5 100644 --- a/pkg/build/controller/buildpod/controller.go +++ b/pkg/build/controller/buildpod/controller.go @@ -11,6 +11,7 @@ import ( "k8s.io/kubernetes/pkg/client/cache" kclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" kcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" + "k8s.io/kubernetes/pkg/client/record" kcontroller "k8s.io/kubernetes/pkg/controller" utilruntime "k8s.io/kubernetes/pkg/util/runtime" "k8s.io/kubernetes/pkg/util/wait" @@ -49,16 +50,22 @@ type BuildPodController struct { podStoreSynced func() bool runPolicies []policy.RunPolicy + + recorder record.EventRecorder } // NewBuildPodController creates a new BuildPodController. func NewBuildPodController(buildInformer, podInformer cache.SharedIndexInformer, kc kclientset.Interface, oc osclient.Interface) *BuildPodController { + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartRecordingToSink(&kcoreclient.EventSinkImpl{Interface: kc.Core().Events("")}) + buildListerUpdater := buildclient.NewOSClientBuildClient(oc) c := &BuildPodController{ buildUpdater: buildListerUpdater, secretClient: kc.Core(), // TODO: Replace with cache client podClient: kc.Core(), queue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()), + recorder: eventBroadcaster.NewRecorder(kapi.EventSource{Component: "build-pod-controller"}), } c.runPolicies = policy.GetAllRunPolicies(buildListerUpdater, buildListerUpdater) @@ -197,6 +204,7 @@ func (bc *BuildPodController) HandlePod(pod *kapi.Pod) error { if build.Status.Phase == buildapi.BuildPhaseRunning { now := unversioned.Now() build.Status.StartTimestamp = &now + bc.recorder.Eventf(build, kapi.EventTypeNormal, buildapi.BuildStartedEventReason, fmt.Sprintf(buildapi.BuildStartedEventMessage, build.Namespace, build.Name)) } } @@ -218,6 +226,12 @@ func (bc *BuildPodController) HandlePod(pod *kapi.Pod) error { // handle completion for it. otherwise ignore it because we've already // handled its completion previously. if !buildWasComplete && buildutil.IsBuildComplete(build) { + switch build.Status.Phase { + case buildapi.BuildPhaseComplete: + bc.recorder.Eventf(build, kapi.EventTypeNormal, buildapi.BuildCompletedEventReason, fmt.Sprintf(buildapi.BuildCompletedEventMessage, build.Namespace, build.Name)) + case buildapi.BuildPhaseError, buildapi.BuildPhaseFailed: + bc.recorder.Eventf(build, kapi.EventTypeNormal, buildapi.BuildFailedEventReason, fmt.Sprintf(buildapi.BuildFailedEventMessage, build.Namespace, build.Name)) + } common.HandleBuildCompletion(build, bc.runPolicies) } @@ -259,6 +273,8 @@ func (bc *BuildPodController) HandleBuildPodDeletion(pod *kapi.Pod) error { build.Status.Reason = buildapi.StatusReasonBuildPodDeleted build.Status.Message = buildapi.StatusMessageBuildPodDeleted common.SetBuildCompletionTimeAndDuration(build) + bc.recorder.Eventf(build, kapi.EventTypeNormal, buildapi.BuildFailedEventReason, fmt.Sprintf(buildapi.BuildFailedEventMessage, build.Namespace, build.Name)) + if err := bc.buildUpdater.Update(build.Namespace, build); err != nil { return fmt.Errorf("Failed to update build %s/%s: %v", build.Namespace, build.Name, err) } diff --git a/pkg/build/controller/controller.go b/pkg/build/controller/controller.go index c1de7e869378..3455a4ccb939 100644 --- a/pkg/build/controller/controller.go +++ b/pkg/build/controller/controller.go @@ -72,6 +72,7 @@ func (bc *BuildController) CancelBuild(build *buildapi.Build) error { build.Status.Phase = buildapi.BuildPhaseCancelled common.SetBuildCompletionTimeAndDuration(build) + bc.Recorder.Eventf(build, kapi.EventTypeNormal, buildapi.BuildCancelledEventReason, fmt.Sprintf(buildapi.BuildCancelledEventMessage, build.Namespace, build.Name)) // set the status details for the cancelled build before updating the build // object. build.Status.Reason = buildapi.StatusReasonCancelledBuild diff --git a/test/common/build/controllers.go b/test/common/build/controllers.go index cfc0eb7c9a7c..e6dd97735be4 100644 --- a/test/common/build/controllers.go +++ b/test/common/build/controllers.go @@ -624,6 +624,24 @@ func RunBuildRunningPodDeleteTest(t testingT, clusterAdminClient *client.Client, if newBuild.Status.Phase != buildapi.BuildPhaseError { t.Fatalf("expected build status to be marked error, but was marked %s", newBuild.Status.Phase) } + events, err := clusterAdminKubeClientset.Core().Events(testutil.Namespace()).Search(newBuild) + if err != nil { + t.Fatalf("error getting build events: %v", err) + } + foundFailed := false + for _, event := range events.Items { + if event.Reason == buildapi.BuildFailedEventReason { + foundFailed = true + expect := fmt.Sprintf(buildapi.BuildFailedEventMessage, newBuild.Namespace, newBuild.Name) + if event.Message != expect { + t.Fatalf("expected failed event message to be %s, got %s", expect, event.Message) + } + break + } + } + if !foundFailed { + t.Fatalf("expected to find a failed event on the build %s/%s", newBuild.Namespace, newBuild.Name) + } } func RunBuildCompletePodDeleteTest(t testingT, clusterAdminClient *client.Client, clusterAdminKubeClientset *kclientset.Clientset) { diff --git a/test/extended/builds/failure_status.go b/test/extended/builds/failure_status.go index b42a80262536..ac2753f29e1b 100644 --- a/test/extended/builds/failure_status.go +++ b/test/extended/builds/failure_status.go @@ -53,6 +53,8 @@ var _ = g.Describe("[builds][Slow] update failure status", func() { o.Expect(err).NotTo(o.HaveOccurred()) o.Expect(build.Status.Reason).To(o.Equal(buildapi.StatusReasonPostCommitHookFailed)) o.Expect(build.Status.Message).To(o.Equal(buildapi.StatusMessagePostCommitHookFailed)) + + exutil.CheckForBuildEvent(oc.KubeClient().Core(), br.Build, buildapi.BuildFailedEventReason, buildapi.BuildFailedEventMessage) }) }) @@ -70,6 +72,8 @@ var _ = g.Describe("[builds][Slow] update failure status", func() { o.Expect(err).NotTo(o.HaveOccurred()) o.Expect(build.Status.Reason).To(o.Equal(buildapi.StatusReasonFetchSourceFailed)) o.Expect(build.Status.Message).To(o.Equal(buildapi.StatusMessageFetchSourceFailed)) + + exutil.CheckForBuildEvent(oc.KubeClient().Core(), br.Build, buildapi.BuildFailedEventReason, buildapi.BuildFailedEventMessage) }) }) @@ -87,6 +91,8 @@ var _ = g.Describe("[builds][Slow] update failure status", func() { o.Expect(err).NotTo(o.HaveOccurred()) o.Expect(build.Status.Reason).To(o.Equal(buildapi.StatusReasonFetchSourceFailed)) o.Expect(build.Status.Message).To(o.Equal(buildapi.StatusMessageFetchSourceFailed)) + + exutil.CheckForBuildEvent(oc.KubeClient().Core(), br.Build, buildapi.BuildFailedEventReason, buildapi.BuildFailedEventMessage) }) }) @@ -104,6 +110,8 @@ var _ = g.Describe("[builds][Slow] update failure status", func() { o.Expect(err).NotTo(o.HaveOccurred()) o.Expect(build.Status.Reason).To(o.Equal(buildapi.StatusReasonPullBuilderImageFailed)) o.Expect(build.Status.Message).To(o.Equal(buildapi.StatusMessagePullBuilderImageFailed)) + + exutil.CheckForBuildEvent(oc.KubeClient().Core(), br.Build, buildapi.BuildFailedEventReason, buildapi.BuildFailedEventMessage) }) }) @@ -121,6 +129,8 @@ var _ = g.Describe("[builds][Slow] update failure status", func() { o.Expect(err).NotTo(o.HaveOccurred()) o.Expect(build.Status.Reason).To(o.Equal(buildapi.StatusReasonPushImageToRegistryFailed)) o.Expect(build.Status.Message).To(o.Equal(buildapi.StatusMessagePushImageToRegistryFailed)) + + exutil.CheckForBuildEvent(oc.KubeClient().Core(), br.Build, buildapi.BuildFailedEventReason, buildapi.BuildFailedEventMessage) }) }) @@ -138,6 +148,8 @@ var _ = g.Describe("[builds][Slow] update failure status", func() { o.Expect(err).NotTo(o.HaveOccurred()) o.Expect(build.Status.Reason).To(o.Equal(reasonAssembleFailed)) o.Expect(build.Status.Message).To(o.Equal(messageAssembleFailed)) + + exutil.CheckForBuildEvent(oc.KubeClient().Core(), br.Build, buildapi.BuildFailedEventReason, buildapi.BuildFailedEventMessage) }) }) @@ -155,6 +167,8 @@ var _ = g.Describe("[builds][Slow] update failure status", func() { o.Expect(err).NotTo(o.HaveOccurred()) o.Expect(build.Status.Reason).To(o.Equal(reasonFetchRuntimeArtifacts)) o.Expect(build.Status.Message).To(o.Equal(messageFetchRuntimeArtifacts)) + + exutil.CheckForBuildEvent(oc.KubeClient().Core(), br.Build, buildapi.BuildFailedEventReason, buildapi.BuildFailedEventMessage) }) }) @@ -172,6 +186,8 @@ var _ = g.Describe("[builds][Slow] update failure status", func() { o.Expect(err).NotTo(o.HaveOccurred()) o.Expect(build.Status.Reason).To(o.Equal(buildapi.StatusReasonGenericBuildFailed)) o.Expect(build.Status.Message).To(o.Equal(buildapi.StatusMessageGenericBuildFailed)) + + exutil.CheckForBuildEvent(oc.KubeClient().Core(), br.Build, buildapi.BuildFailedEventReason, buildapi.BuildFailedEventMessage) }) }) }) diff --git a/test/extended/builds/s2i_quota.go b/test/extended/builds/s2i_quota.go index ad4edbafebaf..c16dbfa37fc3 100644 --- a/test/extended/builds/s2i_quota.go +++ b/test/extended/builds/s2i_quota.go @@ -6,6 +6,7 @@ import ( g "github.com/onsi/ginkgo" o "github.com/onsi/gomega" + buildapi "github.com/openshift/origin/pkg/build/api" exutil "github.com/openshift/origin/test/extended/util" ) @@ -52,6 +53,14 @@ var _ = g.Describe("[builds][Conformance] s2i build with a quota", func() { o.Expect(buildLog).To(o.ContainSubstring("SHARES=61")) o.Expect(buildLog).To(o.ContainSubstring("PERIOD=100000")) o.Expect(buildLog).To(o.ContainSubstring("QUOTA=6000")) + + events, err := oc.KubeClient().Core().Events(oc.Namespace()).Search(br.Build) + o.Expect(err).NotTo(o.HaveOccurred(), "Should be able to get events from the build") + o.Expect(events).NotTo(o.BeNil(), "Build event list should not be nil") + + exutil.CheckForBuildEvent(oc.KubeClient().Core(), br.Build, buildapi.BuildStartedEventReason, buildapi.BuildStartedEventMessage) + exutil.CheckForBuildEvent(oc.KubeClient().Core(), br.Build, buildapi.BuildCompletedEventReason, buildapi.BuildCompletedEventMessage) + }) }) }) diff --git a/test/extended/builds/start.go b/test/extended/builds/start.go index 32d4d9f3887b..9b7269ba379a 100644 --- a/test/extended/builds/start.go +++ b/test/extended/builds/start.go @@ -216,10 +216,11 @@ var _ = g.Describe("[builds][Slow] starting a build using CLI", func() { err := exutil.WaitForABuild(oc.Client().Builds(oc.Namespace()), "sample-build-binary-invalidnodeselector-1", nil, nil, cancelFn) o.Expect(err).NotTo(o.HaveOccurred()) o.Expect(build.Status.Phase).To(o.Equal(buildapi.BuildPhaseCancelled)) + exutil.CheckForBuildEvent(oc.KubeClient().Core(), build, buildapi.BuildCancelledEventReason, buildapi.BuildCancelledEventMessage) }) }) - g.Describe("cancelling build started by oc start-build --wait", func() { + g.Describe("cancel a build started by oc start-build --wait", func() { g.It("should start a build and wait for the build to cancel", func() { g.By("starting the build with --wait flag") var wg sync.WaitGroup @@ -247,11 +248,16 @@ var _ = g.Describe("[builds][Slow] starting a build using CLI", func() { }) o.Expect(buildName).ToNot(o.BeEmpty()) + build, err := oc.Client().Builds(oc.Namespace()).Get(buildName) + o.Expect(err).NotTo(o.HaveOccurred()) + o.Expect(build).NotTo(o.BeNil(), "build object should exist") g.By(fmt.Sprintf("cancelling the build %q", buildName)) - err := oc.Run("cancel-build").Args(buildName).Execute() + err = oc.Run("cancel-build").Args(buildName).Execute() o.Expect(err).ToNot(o.HaveOccurred()) wg.Wait() + exutil.CheckForBuildEvent(oc.KubeClient().Core(), build, buildapi.BuildCancelledEventReason, buildapi.BuildCancelledEventMessage) + }) }) diff --git a/test/extended/util/framework.go b/test/extended/util/framework.go index 2426c5abb0ba..1ea62893598d 100644 --- a/test/extended/util/framework.go +++ b/test/extended/util/framework.go @@ -1400,3 +1400,19 @@ func CreateExecPodOnNode(client kcoreclient.CoreInterface, ns, nodeName, name st o.Expect(err).NotTo(o.HaveOccurred()) return created.Name } + +func CheckForBuildEvent(client kcoreclient.CoreInterface, build *buildapi.Build, reason, message string) { + events, err := client.Events(build.Namespace).Search(build) + o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred(), "Should be able to get events from the build") + o.ExpectWithOffset(1, events).NotTo(o.BeNil(), "Build event list should not be nil") + + found := false + for _, event := range events.Items { + framework.Logf("Found event %#v", event) + if reason == event.Reason { + found = true + o.ExpectWithOffset(1, event.Message).To(o.Equal(fmt.Sprintf(message, build.Namespace, build.Name))) + } + } + o.ExpectWithOffset(1, found).To(o.BeTrue(), "Did not find a %q event on build %s/%s", reason, build.Namespace, build.Name) +}