feat(app) add k8s event for Application (#1463)

add k8s event for Application

fixed #1424

Signed-off-by: zhuhuijun <zhuhuijunzhj@gmail.com>

fix

Signed-off-by: zhuhuijun <zhuhuijunzhj@gmail.com>
This commit is contained in:
Herman Zhu
2021-04-20 12:05:36 +08:00
committed by GitHub
parent 7377e17f8f
commit b726c5291f
4 changed files with 226 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
/*
Copyright 2021 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package types
// reason for Application
const (
ReasonParsed = "Parsed"
ReasonRendered = "Rendered"
ReasonApplied = "Applied"
ReasonHealthCheck = "HealthChecked"
ReasonDeployed = "Deployed"
ReasonFailedParse = "FailedParse"
ReasonFailedRender = "FailedRender"
ReasonFailedApply = "FailedApply"
ReasonFailedHealthCheck = "FailedHealthCheck"
ReasonFailedGC = "FailedGC"
)
// event message for Application
const (
MessageParsed = "Parsed successfully"
MessageRendered = "Rendered successfully"
MessageApplied = "Applied successfully"
MessageHealthCheck = "Health checked healthy"
MessageDeployed = "Deployed successfully"
MessageFailedParse = "fail to parse application, err: %v"
MessageFailedRender = "fail to render application, err: %v"
MessageFailedApply = "fail to apply component, err: %v"
MessageFailedHealthCheck = "fail to health check, err: %v"
MessageFailedGC = "fail to garbage collection, err: %v"
)
@@ -18,6 +18,7 @@ package application
import (
"context"
"fmt"
"time"
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
@@ -25,9 +26,11 @@ import (
"github.com/crossplane/crossplane-runtime/pkg/meta"
"github.com/go-logr/logr"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/retry"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -35,6 +38,7 @@ import (
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
velatypes "github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/appfile"
core "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev"
"github.com/oam-dev/kubevela/pkg/dsl/definition"
@@ -58,6 +62,7 @@ type Reconciler struct {
pd *definition.PackageDiscover
Log logr.Logger
Scheme *runtime.Scheme
Recorder record.EventRecorder
applicator apply.Applicator
appRevisionLimit int
}
@@ -119,6 +124,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
if err != nil {
applog.Error(err, "[Handle Parse]")
app.Status.SetConditions(errorCondition("Parsed", err))
r.Recorder.Event(app, corev1.EventTypeWarning, velatypes.ReasonFailedParse, fmt.Sprintf(velatypes.MessageFailedParse, err))
return handler.handleErr(err)
}
@@ -129,8 +135,11 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
if err != nil {
applog.Error(err, "[Handle Calculate Revision]")
app.Status.SetConditions(errorCondition("Parsed", err))
r.Recorder.Event(app, corev1.EventTypeWarning, velatypes.ReasonFailedParse, fmt.Sprintf(velatypes.MessageFailedParse, err))
return handler.handleErr(err)
}
r.Recorder.Event(app, corev1.EventTypeNormal, velatypes.ReasonParsed, velatypes.MessageParsed)
// Record the revision so it can be used to render data in context.appRevision
generatedAppfile.RevisionName = appRev.Name
@@ -140,6 +149,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
if err != nil {
applog.Error(err, "[Handle GenerateApplicationConfiguration]")
app.Status.SetConditions(errorCondition("Built", err))
r.Recorder.Event(app, corev1.EventTypeWarning, velatypes.ReasonFailedRender, fmt.Sprintf(velatypes.MessageFailedRender, err))
return handler.handleErr(err)
}
@@ -147,6 +157,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
if err != nil {
applog.Error(err, "[Handle resourceTracker]")
app.Status.SetConditions(errorCondition("Handle resourceTracker", err))
r.Recorder.Event(app, corev1.EventTypeWarning, velatypes.ReasonFailedRender, fmt.Sprintf(velatypes.MessageFailedRender, err))
return handler.handleErr(err)
}
@@ -154,15 +165,18 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
oamutil.PassLabelAndAnnotation(app, ac)
app.Status.SetConditions(readyCondition("Built"))
r.Recorder.Event(app, corev1.EventTypeNormal, velatypes.ReasonRendered, velatypes.MessageRendered)
applog.Info("apply application revision & component to the cluster")
// apply application revision & component to the cluster
if err := handler.apply(ctx, appRev, ac, comps); err != nil {
applog.Error(err, "[Handle apply]")
app.Status.SetConditions(errorCondition("Applied", err))
r.Recorder.Event(app, corev1.EventTypeWarning, velatypes.ReasonFailedApply, fmt.Sprintf(velatypes.MessageFailedApply, err))
return handler.handleErr(err)
}
app.Status.SetConditions(readyCondition("Applied"))
r.Recorder.Event(app, corev1.EventTypeNormal, velatypes.ReasonApplied, velatypes.MessageApplied)
app.Status.Phase = common.ApplicationHealthChecking
applog.Info("check application health status")
// check application health status
@@ -170,6 +184,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
if err != nil {
applog.Error(err, "[status aggregate]")
app.Status.SetConditions(errorCondition("HealthCheck", err))
r.Recorder.Event(app, corev1.EventTypeWarning, velatypes.ReasonFailedHealthCheck, fmt.Sprintf(velatypes.MessageFailedHealthCheck, err))
return handler.handleErr(err)
}
if !healthy {
@@ -181,11 +196,15 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
}
app.Status.Services = appCompStatus
app.Status.SetConditions(readyCondition("HealthCheck"))
r.Recorder.Event(app, corev1.EventTypeNormal, velatypes.ReasonHealthCheck, velatypes.MessageHealthCheck)
app.Status.Phase = common.ApplicationRunning
err = garbageCollection(ctx, handler)
if err != nil {
applog.Error(err, "[Garbage collection]")
r.Recorder.Event(app, corev1.EventTypeWarning, velatypes.ReasonFailedGC, fmt.Sprintf(velatypes.MessageFailedGC, err))
}
// Gather status of components
var refComps []v1alpha1.TypedReference
for _, comp := range comps {
@@ -197,6 +216,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
})
}
app.Status.Components = refComps
r.Recorder.Event(app, corev1.EventTypeNormal, velatypes.ReasonDeployed, velatypes.MessageDeployed)
return ctrl.Result{}, r.UpdateStatus(ctx, app)
}
@@ -235,6 +255,7 @@ func Setup(mgr ctrl.Manager, args core.Args, _ logging.Logger) error {
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("Application"),
Scheme: mgr.GetScheme(),
Recorder: mgr.GetEventRecorderFor("Application"),
dm: args.DiscoveryMapper,
pd: args.PackageDiscover,
applicator: apply.NewAPIApplicator(mgr.GetClient()),
@@ -26,6 +26,8 @@ import (
"strconv"
"time"
velatypes "github.com/oam-dev/kubevela/apis/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@@ -94,6 +96,16 @@ var _ = Describe("Test Application Controller", func() {
},
}
appFailParse := appwithNoTrait.DeepCopy()
appFailParse.SetName("app-fail-to-parsed")
appFailParse.Spec.Components[0].Type = "fakeWorker"
appFailRender := appwithNoTrait.DeepCopy()
appFailRender.SetName("app-fail-to-render")
appFailRender.Spec.Components[0].Properties = runtime.RawExtension{
Raw: []byte(`{"cmd1":["sleep","1000"],"image1":"busybox"}`),
}
appImportPkg := &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
@@ -249,6 +261,73 @@ var _ = Describe("Test Application Controller", func() {
By("[TEST] Clean up resources after an integration test")
})
It("app step will set event", func() {
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "vela-test-app-without-trait-event",
},
}
appwithNoTrait.SetNamespace(ns.Name)
Expect(k8sClient.Create(ctx, ns)).Should(BeNil())
Expect(k8sClient.Create(ctx, appwithNoTrait.DeepCopyObject())).Should(BeNil())
appKey := client.ObjectKey{
Name: appwithNoTrait.Name,
Namespace: appwithNoTrait.Namespace,
}
reconcileRetry(reconciler, reconcile.Request{NamespacedName: appKey})
events, err := recorder.GetEventsWithName(appwithNoTrait.Name)
Expect(err).Should(BeNil())
Expect(len(events)).ShouldNot(Equal(0))
for _, event := range events {
Expect(event.EventType).ShouldNot(Equal(corev1.EventTypeWarning))
Expect(event.EventType).Should(Equal(corev1.EventTypeNormal))
}
// fail to parse application
appFailParse.SetNamespace(ns.Name)
appFailParseKey := client.ObjectKey{
Name: appFailParse.Name,
Namespace: appFailParse.Namespace,
}
Expect(k8sClient.Create(ctx, appFailParse.DeepCopyObject())).Should(BeNil())
reconcileOnce(reconciler, reconcile.Request{NamespacedName: appFailParseKey})
parseEvents, err := recorder.GetEventsWithName(appFailParse.Name)
Expect(err).Should(BeNil())
Expect(len(parseEvents)).Should(Equal(1))
for _, event := range parseEvents {
Expect(event.EventType).Should(Equal(corev1.EventTypeWarning))
Expect(event.Reason).Should(Equal(velatypes.ReasonFailedParse))
}
// fail to render application
appFailRender.SetNamespace(ns.Name)
appFailRenderKey := client.ObjectKey{
Name: appFailRender.Name,
Namespace: appFailRender.Namespace,
}
Expect(k8sClient.Create(ctx, appFailRender.DeepCopyObject())).Should(BeNil())
reconcileOnce(reconciler, reconcile.Request{NamespacedName: appFailRenderKey})
renderEvents, err := recorder.GetEventsWithName(appFailRender.Name)
Expect(err).Should(BeNil())
Expect(len(renderEvents)).Should(Equal(2))
var count int
for _, event := range renderEvents {
if event.EventType == corev1.EventTypeWarning {
Expect(event.Reason).Should(Equal(velatypes.ReasonFailedRender))
count++
}
}
Expect(count).Should(Equal(1))
})
It("app can consume db secret generated by other application", func() {
var (
appName = "webapp"
@@ -1524,6 +1603,10 @@ func reconcileRetry(r reconcile.Reconciler, req reconcile.Request) {
}, 30*time.Second, time.Second).Should(BeNil())
}
func reconcileOnce(r reconcile.Reconciler, req reconcile.Request) {
r.Reconcile(req)
}
const (
scopeDefYaml = `apiVersion: core.oam.dev/v1beta1
kind: ScopeDefinition
@@ -18,12 +18,17 @@ package application
import (
"context"
"fmt"
"math/rand"
"os"
"path/filepath"
"testing"
"time"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/api/meta"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@@ -55,6 +60,7 @@ import (
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
var cfg *rest.Config
var recorder = NewFakeRecorder(10000)
var k8sClient client.Client
var testEnv *envtest.Environment
var testScheme = runtime.NewScheme()
@@ -128,6 +134,7 @@ var _ = BeforeSuite(func(done Done) {
Scheme: testScheme,
dm: dm,
pd: pd,
Recorder: recorder,
appRevisionLimit: appRevisionLimit,
}
// setup the controller manager since we need the component handler to run in the background
@@ -168,3 +175,71 @@ var _ = AfterSuite(func() {
Expect(err).ToNot(HaveOccurred())
close(stop)
})
type FakeRecorder struct {
Events chan string
Message map[string][]*Events
}
type Events struct {
Name string
Namespace string
EventType string
Reason string
Message string
}
func (f *FakeRecorder) Event(object runtime.Object, eventtype, reason, message string) {
if f.Events != nil {
objectMeta, err := meta.Accessor(object)
if err != nil {
return
}
event := &Events{
Name: objectMeta.GetName(),
Namespace: objectMeta.GetNamespace(),
EventType: eventtype,
Reason: reason,
Message: message,
}
records, ok := f.Message[objectMeta.GetName()]
if !ok {
f.Message[objectMeta.GetName()] = []*Events{event}
return
}
records = append(records, event)
f.Message[objectMeta.GetName()] = records
}
}
func (f *FakeRecorder) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) {
if f.Events != nil {
f.Events <- fmt.Sprintf(eventtype+" "+reason+" "+messageFmt, args...)
}
}
func (f *FakeRecorder) AnnotatedEventf(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) {
f.Eventf(object, eventtype, reason, messageFmt, args...)
}
func (f *FakeRecorder) GetEventsWithName(name string) ([]*Events, error) {
records, ok := f.Message[name]
if !ok {
return nil, errors.New("not found events")
}
return records, nil
}
// NewFakeRecorder creates new fake event recorder with event channel with
// buffer of given size.
func NewFakeRecorder(bufferSize int) *FakeRecorder {
return &FakeRecorder{
Events: make(chan string, bufferSize),
Message: make(map[string][]*Events),
}
}