mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 04:26:39 +00:00
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:
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user