diff --git a/pkg/build/api/types.go b/pkg/build/api/types.go index 29d4702df9dd..b953a42a309f 100644 --- a/pkg/build/api/types.go +++ b/pkg/build/api/types.go @@ -63,6 +63,31 @@ const ( // forces the build to be processed by the build controller queue without waiting // for a resync. BuildAcceptedAnnotation = "build.openshift.io/accepted" + + // BuildCreatedEvent is the reason associated with the event registered when a build is created. + BuildCreatedEventReason = "BuildCreated" + // BuildCreatedEventMessage is the message associatd with the event registered when a build is created. + BuildCreatedEventMessage = "Build %s/%s has been created" + // BuildStartedEvent is the reason associated with the event registered when a build is started (pod is created). + BuildStartedEventReason = "BuildStarted" + // BuildStartedEvent is the message associated with the event registered when a build is started (pod is created). + BuildStartedEventMessage = "Pod has been created to run build %s/%s" + // BuildRunningEvent is the reason associated with the event registered when the build pod starts running. + BuildRunningEventReason = "BuildRunning" + // BuildRunningEvent is the message associated with the event registered when the build pod starts running. + BuildRunningEventMessage = "Pod for build %s/%s started running" + // BuildCompletedEvent is the reason associated with the event registered when build completes successfully. + BuildCompletedEventReason = "BuildCompleted" + // BuildCompletedEvent is the message associated with the event registered when build completes successfully. + BuildCompletedEventMessage = "Build %s/%s completed successfully" + // BuildFailedEvent is the reason associated with the event registered when build fails. + BuildFailedEventReason = "BuildFailed" + // BuildFailedEvent is the message associated with the event registered when build fails. + BuildFailedEventMessage = "Build %s/%s failed" + // BuildCancelledEvent is the reason associated with the event registered when build is cancelled. + BuildCancelledEventReason = "BuildCancelled" + // BuildCancelledEvent 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..226adddf37e4 100644 --- a/pkg/build/client/clients.go +++ b/pkg/build/client/clients.go @@ -37,6 +37,11 @@ func (c OSClientBuildConfigClient) Update(buildConfig *buildapi.BuildConfig) err return err } +// BuildGetter provides methods for getting existing Builds. +type BuildGetter interface { + Get(namespace, name string) (*buildapi.Build, error) +} + // BuildUpdater provides methods for updating existing Builds. type BuildUpdater interface { Update(namespace string, build *buildapi.Build) error @@ -47,7 +52,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 } @@ -57,6 +62,11 @@ func NewOSClientBuildClient(client osclient.Interface) *OSClientBuildClient { return &OSClientBuildClient{Client: client} } +// Get returns a Build using the OpenShift client. +func (c OSClientBuildClient) Get(namespace, name string) (*buildapi.Build, error) { + return c.Client.Builds(namespace).Get(name) +} + // Update updates builds using the OpenShift client. func (c OSClientBuildClient) Update(namespace string, build *buildapi.Build) error { _, e := c.Client.Builds(namespace).Update(build) diff --git a/pkg/build/controller/buildpod/controller.go b/pkg/build/controller/buildpod/controller.go index 8b6d1dea2721..156540a97ac8 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) @@ -189,10 +196,17 @@ func (bc *BuildPodController) HandlePod(pod *kapi.Pod) error { if buildutil.IsBuildComplete(build) { common.SetBuildCompletionTimeAndDuration(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)) + } } if build.Status.Phase == buildapi.BuildPhaseRunning { now := unversioned.Now() build.Status.StartTimestamp = &now + bc.recorder.Eventf(build, kapi.EventTypeNormal, buildapi.BuildRunningEventReason, fmt.Sprintf(buildapi.BuildRunningEventMessage, 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) @@ -241,6 +255,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..24406432c941 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 @@ -219,8 +220,9 @@ func (bc *BuildController) nextBuildPhase(build *buildapi.Build) error { build.Status.Message = buildapi.StatusMessageCannotCreateBuildPod return fmt.Errorf("failed to create build pod: %v", err) } - common.SetBuildPodNameAnnotation(build, podSpec.Name) glog.V(4).Infof("Created pod for build: %#v", podSpec) + bc.Recorder.Eventf(build, kapi.EventTypeNormal, buildapi.BuildStartedEventReason, fmt.Sprintf(buildapi.BuildStartedEventMessage, build.Namespace, build.Name)) + common.SetBuildPodNameAnnotation(build, podSpec.Name) // Set the build phase, which will be persisted. build.Status.Phase = buildapi.BuildPhasePending diff --git a/pkg/build/generator/generator.go b/pkg/build/generator/generator.go index 9ab51305051e..e426988bf069 100644 --- a/pkg/build/generator/generator.go +++ b/pkg/build/generator/generator.go @@ -13,10 +13,12 @@ import ( "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/unversioned" kcoreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" + "k8s.io/kubernetes/pkg/client/record" "k8s.io/kubernetes/pkg/credentialprovider" kvalidation "k8s.io/kubernetes/pkg/util/validation" buildapi "github.com/openshift/origin/pkg/build/api" + buildclient "github.com/openshift/origin/pkg/build/client" buildutil "github.com/openshift/origin/pkg/build/util" "github.com/openshift/origin/pkg/cmd/admin/policy" "github.com/openshift/origin/pkg/cmd/server/bootstrappolicy" @@ -49,6 +51,8 @@ type BuildGenerator struct { DefaultServiceAccountName string ServiceAccounts kcoreclient.ServiceAccountsGetter Secrets kcoreclient.SecretsGetter + Recorder record.EventRecorder + Builds buildclient.BuildGetter } // GeneratorClient is the API client used by the generator @@ -430,7 +434,12 @@ func (g *BuildGenerator) createBuild(ctx kapi.Context, build *buildapi.Build) (* if err != nil { return nil, err } - return g.Client.GetBuild(ctx, build.Name) + b, err := g.Builds.Get(build.Namespace, build.Name) + //b, err := g.Client.GetBuild(ctx, build.Name) + if err == nil { + g.Recorder.Eventf(build, kapi.EventTypeNormal, buildapi.BuildCreatedEventReason, fmt.Sprintf(buildapi.BuildCreatedEventMessage, build.Namespace, build.Name)) + } + return b, err } // generateBuildFromConfig generates a build definition based on the current imageid diff --git a/pkg/cmd/server/origin/master.go b/pkg/cmd/server/origin/master.go index ae3e95ce15e1..21501a29a022 100644 --- a/pkg/cmd/server/origin/master.go +++ b/pkg/cmd/server/origin/master.go @@ -28,6 +28,8 @@ import ( "k8s.io/kubernetes/pkg/apiserver" kapiserverfilters "k8s.io/kubernetes/pkg/apiserver/filters" 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" "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/genericapiserver" kgenericfilters "k8s.io/kubernetes/pkg/genericapiserver/filters" @@ -743,6 +745,9 @@ func (c *MasterConfig) GetRestStorage() map[unversioned.GroupVersion]map[string] imageStreamImageStorage := imagestreamimage.NewREST(imageRegistry, imageStreamRegistry) imageStreamImageRegistry := imagestreamimage.NewRegistry(imageStreamImageStorage) + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartRecordingToSink(&kcoreclient.EventSinkImpl{Interface: c.KubeClientset().Core().Events("")}) + generatorBuildClient, _ := c.BuildControllerClients() buildGenerator := &buildgenerator.BuildGenerator{ Client: buildgenerator.Client{ GetBuildConfigFunc: buildConfigRegistry.GetBuildConfig, @@ -756,6 +761,8 @@ func (c *MasterConfig) GetRestStorage() map[unversioned.GroupVersion]map[string] }, ServiceAccounts: c.KubeClientset(), Secrets: c.KubeClientset(), + Builds: buildclient.NewOSClientBuildClient(generatorBuildClient), + Recorder: eventBroadcaster.NewRecorder(kapi.EventSource{Component: "build-generator"}), } // TODO: with sharding, this needs to be changed diff --git a/test/common/build/controllers.go b/test/common/build/controllers.go index c731d2af1f07..c242a8e26bbe 100644 --- a/test/common/build/controllers.go +++ b/test/common/build/controllers.go @@ -612,6 +612,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 { + switch event.Reason { + case 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) + } + } + } + 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..6363117bf1cd 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.CheckForEvent(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.CheckForEvent(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.CheckForEvent(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.CheckForEvent(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.CheckForEvent(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.CheckForEvent(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.CheckForEvent(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.CheckForEvent(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..186d6fc5ba0b 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,16 @@ 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.CheckForEvent(oc.KubeClient().Core(), br.Build, buildapi.BuildCreatedEventReason, buildapi.BuildCreatedEventMessage) + exutil.CheckForEvent(oc.KubeClient().Core(), br.Build, buildapi.BuildStartedEventReason, buildapi.BuildStartedEventMessage) + exutil.CheckForEvent(oc.KubeClient().Core(), br.Build, buildapi.BuildRunningEventReason, buildapi.BuildRunningEventMessage) + exutil.CheckForEvent(oc.KubeClient().Core(), br.Build, buildapi.BuildFailedEventReason, buildapi.BuildFailedEventMessage) + }) }) }) diff --git a/test/extended/builds/start.go b/test/extended/builds/start.go index 32d4d9f3887b..982c826a6eb0 100644 --- a/test/extended/builds/start.go +++ b/test/extended/builds/start.go @@ -216,6 +216,7 @@ 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.CheckForEvent(oc.KubeClient().Core(), build, buildapi.BuildCancelledEventReason, buildapi.BuildCancelledEventMessage) }) }) @@ -247,9 +248,13 @@ 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") + exutil.CheckForEvent(oc.KubeClient().Core(), build, buildapi.BuildCancelledEventReason, buildapi.BuildCancelledEventMessage) 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() }) diff --git a/test/extended/util/framework.go b/test/extended/util/framework.go index 2426c5abb0ba..8a38d8eb0991 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 CheckForEvent(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") + + failed := false + for _, event := range events.Items { + switch event.Reason { + case reason: + failed = true + o.ExpectWithOffset(1, event.Message).To(o.Equal(fmt.Sprintf(message, build.Namespace, build.Name))) + } + } + o.ExpectWithOffset(1, failed).To(o.BeTrue(), "Did not find a %q event on build %s/%s", reason, build.Namespace, build.Name) +}