garbage collection mechanism for AppRevision (#1501)

* WIP refactor gc func

WIP write gc code

WIP update chart,cmd args

add cleanupRevison func into garbageCollection

add more test

finish unit-test

refactor use func implements

Complete e2e test

WIP rewrite some logic

add func test and rewirte context pointing to func

fix cilint

refactor some function

fix typo

fix ci error

change gc logic

after change gc number, fix all test

add check appRevision collection

WIP finish most code

* add rollout batch to fix rollout e2e-tet

* fix component name in rollout

* ignore gc error, just log the error
This commit is contained in:
wyike
2021-04-16 16:46:41 +08:00
committed by GitHub
parent 0333b9e891
commit ebc8476a31
14 changed files with 1179 additions and 21 deletions

View File

@@ -156,7 +156,7 @@ docker-push:
e2e-setup:
helm install --create-namespace -n flux-system helm-flux http://oam.dev/catalog/helm-flux2-0.1.0.tgz
helm install kruise https://github.com/openkruise/kruise/releases/download/v0.7.0/kruise-chart.tgz
helm upgrade --install --create-namespace --namespace vela-system --set image.pullPolicy=IfNotPresent --set image.repository=vela-core-test --set image.tag=$(GIT_COMMIT) --wait kubevela ./charts/vela-core
helm upgrade --install --create-namespace --namespace vela-system --set image.pullPolicy=IfNotPresent --set image.repository=vela-core-test --set applicationRevisionLimit=5 --set image.tag=$(GIT_COMMIT) --wait kubevela ./charts/vela-core
ginkgo version
ginkgo -v -r e2e/setup
kubectl wait --for=condition=Ready pod -l app.kubernetes.io/name=vela-core,app.kubernetes.io/instance=kubevela -n vela-system --timeout=600s

View File

@@ -121,6 +121,7 @@ spec:
- "--disable-caps={{ .Values.disableCaps }}"
{{ end }}
- "--system-definition-namespace={{ .Values.systemDefinitionNamespace }}"
- "--application-revision-limit={{ .Values.applicationRevisionLimit }}"
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
imagePullPolicy: {{ quote .Values.image.pullPolicy }}
resources:

View File

@@ -80,3 +80,5 @@ admissionWebhooks:
systemDefinitionNamespace: vela-system
applicationRevisionLimit: 10

View File

@@ -89,6 +89,8 @@ func main() {
flag.BoolVar(&logDebug, "log-debug", false, "Enable debug logs for development purpose")
flag.IntVar(&controllerArgs.RevisionLimit, "revision-limit", 50,
"RevisionLimit is the maximum number of revisions that will be maintained. The default value is 50.")
flag.IntVar(&controllerArgs.AppRevisionLimit, "application-revision-limit", 10,
"application-revision-limit is the maximum number of application useless revisions that will be maintained, if the useless revisions exceed this number, older ones will be GCed first.The default value is 10.")
flag.StringVar(&controllerArgs.CustomRevisionHookURL, "custom-revision-hook-url", "",
"custom-revision-hook-url is a webhook url which will let KubeVela core to call with applicationConfiguration and component info and return a customized component revision")
flag.BoolVar(&controllerArgs.ApplicationConfigurationInstalled, "app-config-installed", true,

View File

@@ -48,6 +48,10 @@ type Args struct {
// The default value is 50.
RevisionLimit int
// AppRevisionLimit is the maximum number of application revisions that will be maintained.
// The default value is 10.
AppRevisionLimit int
// ApplyMode indicates whether workloads and traits should be
// affected if no spec change is made in the ApplicationConfiguration.
ApplyMode ApplyOnceOnlyMode

View File

@@ -54,11 +54,12 @@ const (
// Reconciler reconciles a Application object
type Reconciler struct {
client.Client
dm discoverymapper.DiscoveryMapper
pd *definition.PackageDiscover
Log logr.Logger
Scheme *runtime.Scheme
applicator apply.Applicator
dm discoverymapper.DiscoveryMapper
pd *definition.PackageDiscover
Log logr.Logger
Scheme *runtime.Scheme
applicator apply.Applicator
appRevisionLimit int
}
// +kubebuilder:rbac:groups=core.oam.dev,resources=applications,verbs=get;list;watch;create;update;patch;delete
@@ -181,11 +182,9 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
app.Status.Services = appCompStatus
app.Status.SetConditions(readyCondition("HealthCheck"))
app.Status.Phase = common.ApplicationRunning
err = handler.garbageCollection(ctx)
err = garbageCollection(ctx, handler)
if err != nil {
applog.Error(err, "[Garbage collection]")
app.Status.SetConditions(errorCondition("GarbageCollection", err))
return handler.handleErr(err)
}
// Gather status of components
var refComps []v1alpha1.TypedReference
@@ -233,12 +232,13 @@ func (r *Reconciler) UpdateStatus(ctx context.Context, app *v1beta1.Application,
// Setup adds a controller that reconciles AppRollout.
func Setup(mgr ctrl.Manager, args core.Args, _ logging.Logger) error {
reconciler := Reconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("Application"),
Scheme: mgr.GetScheme(),
dm: args.DiscoveryMapper,
pd: args.PackageDiscover,
applicator: apply.NewAPIApplicator(mgr.GetClient()),
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("Application"),
Scheme: mgr.GetScheme(),
dm: args.DiscoveryMapper,
pd: args.PackageDiscover,
applicator: apply.NewAPIApplicator(mgr.GetClient()),
appRevisionLimit: args.AppRevisionLimit,
}
return reconciler.SetupWithManager(mgr)
}

View File

@@ -587,9 +587,26 @@ func (h *appHandler) recodeTrackedResource(resourceName string, resource runtime
return nil
}
type garbageCollectFunc func(ctx context.Context, h *appHandler) error
// 1. collect useless across-namespace resource
// 2. collect appRevision
func garbageCollection(ctx context.Context, h *appHandler) error {
collectFuncs := []garbageCollectFunc{
garbageCollectFunc(gcAcrossNamespaceResource),
garbageCollectFunc(cleanUpApplicationRevision),
}
for _, collectFunc := range collectFuncs {
if err := collectFunc(ctx, h); err != nil {
return err
}
}
return nil
}
// Now if workloads or traits are in the same namespace with application, applicationContext will take over gc workloads and traits.
// Here we cover the case in witch a cross namespace component or one of its cross namespace trait is removed from an application.
func (h *appHandler) garbageCollection(ctx context.Context) error {
func gcAcrossNamespaceResource(ctx context.Context, h *appHandler) error {
rt := new(v1beta1.ResourceTracker)
err := h.r.Get(ctx, ctypes.NamespacedName{Name: h.generateResourceTrackerName()}, rt)
if err != nil {

View File

@@ -18,9 +18,11 @@ package application
import (
"context"
"sort"
"github.com/pkg/errors"
apiequality "k8s.io/apimachinery/pkg/api/equality"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -291,3 +293,81 @@ func ComputeAppRevisionHash(appRevision *v1beta1.ApplicationRevision) (string, e
// compute the hash of the entire structure
return utils.ComputeSpecHash(&appRevisionHash)
}
// cleanUpApplicationRevision check all appRevisions of the application, remove them if the number of them exceed the limit
func cleanUpApplicationRevision(ctx context.Context, h *appHandler) error {
listOpts := []client.ListOption{
client.InNamespace(h.app.Namespace),
client.MatchingLabels{oam.LabelAppName: h.app.Name},
}
appRevisionList := new(v1beta1.ApplicationRevisionList)
// controller-runtime will cache all appRevision by default, there is no need to watch or own appRevision in manager
if err := h.r.List(ctx, appRevisionList, listOpts...); err != nil {
return err
}
usingRevision, err := gatherUsingAppRevision(ctx, h)
if err != nil {
return err
}
needKill := len(appRevisionList.Items) - h.r.appRevisionLimit - len(usingRevision)
if needKill <= 0 {
return nil
}
h.logger.Info("application controller cleanup old appRevisions", "needKillNum", needKill)
sortedRevision := appRevisionList.Items
sort.Sort(historiesByRevision(sortedRevision))
for _, rev := range sortedRevision {
if needKill <= 0 {
break
}
// we shouldn't delete the revision witch appContext pointing to
if usingRevision[rev.Name] {
continue
}
if err := h.r.Delete(ctx, rev.DeepCopy()); err != nil && !apierrors.IsNotFound(err) {
return err
}
needKill--
}
return nil
}
// gatherUsingAppRevision get all using appRevisions include app's status pointing to and appContext point to
func gatherUsingAppRevision(ctx context.Context, h *appHandler) (map[string]bool, error) {
listOpts := []client.ListOption{
client.InNamespace(h.app.Namespace),
client.MatchingLabels{oam.LabelAppName: h.app.Name},
}
usingRevision := map[string]bool{}
if h.app.Status.LatestRevision != nil && len(h.app.Status.LatestRevision.Name) != 0 {
usingRevision[h.app.Status.LatestRevision.Name] = true
}
appContextList := new(v1alpha2.ApplicationContextList)
err := h.r.List(ctx, appContextList, listOpts...)
if err != nil {
return nil, err
}
for _, appContext := range appContextList.Items {
usingRevision[appContext.Spec.ApplicationRevisionName] = true
}
appDeployUsingRevision, err := utils.CheckAppDeploymentUsingAppRevision(ctx, h.r, h.app.Namespace, h.app.Name)
if err != nil {
return usingRevision, err
}
for _, revName := range appDeployUsingRevision {
usingRevision[revName] = true
}
return usingRevision, nil
}
type historiesByRevision []v1beta1.ApplicationRevision
func (h historiesByRevision) Len() int { return len(h) }
func (h historiesByRevision) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h historiesByRevision) Less(i, j int) bool {
// the appRevision is generated by vela, the error always is nil, so ignore it
ir, _ := util.ExtractRevisionNum(h[i].Name)
ij, _ := util.ExtractRevisionNum(h[j].Name)
return ir < ij
}

View File

@@ -0,0 +1,427 @@
/*
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 application
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/ghodss/yaml"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
var _ = Describe("Test application controller clean up ", func() {
ctx := context.TODO()
namespace := "clean-up-revision"
cd := &v1beta1.ComponentDefinition{}
cdDefJson, _ := yaml.YAMLToJSON([]byte(normalCompDefYaml))
BeforeEach(func() {
ns := v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: namespace,
},
}
Expect(k8sClient.Create(ctx, &ns)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
Expect(json.Unmarshal(cdDefJson, cd)).Should(BeNil())
Expect(k8sClient.Create(ctx, cd.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
})
AfterEach(func() {
By("[TEST] Clean up resources after an integration test")
})
It("Test clean up appRevision", func() {
appName := "app-1"
appKey := types.NamespacedName{Namespace: namespace, Name: appName}
app := getApp(appName, namespace, "normal-worker")
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
checkApp := new(v1beta1.Application)
for i := 0; i < appRevisionLimit+1; i++ {
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
property := fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, i)
checkApp.Spec.Components[0].Properties = runtime.RawExtension{Raw: []byte(property)}
Expect(k8sClient.Update(ctx, checkApp)).Should(BeNil())
_, err := reconciler.Reconcile(ctrl.Request{NamespacedName: appKey})
Expect(err).Should(BeNil())
}
appContext := new(v1alpha2.ApplicationContext)
Expect(k8sClient.Get(ctx, appKey, appContext)).Should(BeNil())
listOpts := []client.ListOption{
client.InNamespace(namespace),
client.MatchingLabels{
oam.LabelAppName: appName,
},
}
appRevisionList := new(v1beta1.ApplicationRevisionList)
Eventually(func() error {
err := k8sClient.List(ctx, appRevisionList, listOpts...)
if err != nil {
return err
}
if len(appRevisionList.Items) != appRevisionLimit+1 {
return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit+1, len(appRevisionList.Items))
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
By("create new appRevision will remove appRevison1")
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
property := fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, 6)
checkApp.Spec.Components[0].Properties = runtime.RawExtension{Raw: []byte(property)}
Expect(k8sClient.Update(ctx, checkApp)).Should(BeNil())
_, err := reconciler.Reconcile(ctrl.Request{NamespacedName: appKey})
Expect(err).Should(BeNil())
deletedRevison := new(v1beta1.ApplicationRevision)
revKey := types.NamespacedName{Namespace: namespace, Name: appName + "-v1"}
Eventually(func() error {
err := k8sClient.List(ctx, appRevisionList, listOpts...)
if err != nil {
return err
}
if len(appRevisionList.Items) != appRevisionLimit+1 {
return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit+1, len(appRevisionList.Items))
}
err = k8sClient.Get(ctx, revKey, deletedRevison)
if err == nil || !apierrors.IsNotFound(err) {
return fmt.Errorf("haven't clean up the oldest revision")
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
By("update app again will gc appRevision2")
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
property = fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, 7)
checkApp.Spec.Components[0].Properties = runtime.RawExtension{Raw: []byte(property)}
Expect(k8sClient.Update(ctx, checkApp)).Should(BeNil())
_, err = reconciler.Reconcile(ctrl.Request{NamespacedName: appKey})
Expect(err).Should(BeNil())
Eventually(func() error {
err := k8sClient.List(ctx, appRevisionList, listOpts...)
if err != nil {
return err
}
if len(appRevisionList.Items) != appRevisionLimit+1 {
return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit+1, len(appRevisionList.Items))
}
revKey = types.NamespacedName{Namespace: namespace, Name: appName + "-v2"}
err = k8sClient.Get(ctx, revKey, deletedRevison)
if err == nil || !apierrors.IsNotFound(err) {
return fmt.Errorf("haven't clean up the revision-2")
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
})
It("Test clean up rollout appRevision", func() {
appName := "app-2"
appKey := types.NamespacedName{Namespace: namespace, Name: appName}
app := getApp(appName, namespace, "normal-worker")
metav1.SetMetaDataAnnotation(&app.ObjectMeta, oam.AnnotationAppRollout, "true")
metav1.SetMetaDataAnnotation(&app.ObjectMeta, oam.AnnotationRollingComponent, "comp1")
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
checkApp := new(v1beta1.Application)
for i := 0; i < appRevisionLimit+1; i++ {
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
property := fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, i)
checkApp.Spec.Components[0].Properties = runtime.RawExtension{Raw: []byte(property)}
Expect(k8sClient.Update(ctx, checkApp)).Should(BeNil())
_, err := reconciler.Reconcile(ctrl.Request{NamespacedName: appKey})
Expect(err).Should(BeNil())
}
appContext := new(v1alpha2.ApplicationContext)
Expect(k8sClient.Get(ctx, appKey, appContext)).Should(util.NotFoundMatcher{})
listOpts := []client.ListOption{
client.InNamespace(namespace),
client.MatchingLabels{
oam.LabelAppName: appName,
},
}
appRevisionList := new(v1beta1.ApplicationRevisionList)
Eventually(func() error {
err := k8sClient.List(ctx, appRevisionList, listOpts...)
if err != nil {
return err
}
if len(appRevisionList.Items) != appRevisionLimit+1 {
return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit+1, len(appRevisionList.Items))
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
By("create new appRevision will remove appRevison1")
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
property := fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, 6)
checkApp.Spec.Components[0].Properties = runtime.RawExtension{Raw: []byte(property)}
Expect(k8sClient.Update(ctx, checkApp)).Should(BeNil())
_, err := reconciler.Reconcile(ctrl.Request{NamespacedName: appKey})
Expect(err).Should(BeNil())
deletedRevison := new(v1beta1.ApplicationRevision)
revKey := types.NamespacedName{Namespace: namespace, Name: appName + "-v1"}
Eventually(func() error {
err := k8sClient.List(ctx, appRevisionList, listOpts...)
if err != nil {
return err
}
if len(appRevisionList.Items) != appRevisionLimit+1 {
return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit+1, len(appRevisionList.Items))
}
err = k8sClient.Get(ctx, revKey, deletedRevison)
if err == nil || !apierrors.IsNotFound(err) {
return fmt.Errorf("haven't clean up the oldest revision")
}
if res, err := util.CheckAppRevision(appRevisionList.Items, []int{2, 3, 4, 5, 6, 7}); err != nil || !res {
return fmt.Errorf("appRevision collection mismatch")
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
By("update app again will gc appRevision2")
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
property = fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, 7)
checkApp.Spec.Components[0].Properties = runtime.RawExtension{Raw: []byte(property)}
Expect(k8sClient.Update(ctx, checkApp)).Should(BeNil())
_, err = reconciler.Reconcile(ctrl.Request{NamespacedName: appKey})
Expect(err).Should(BeNil())
Eventually(func() error {
err := k8sClient.List(ctx, appRevisionList, listOpts...)
if err != nil {
return err
}
if len(appRevisionList.Items) != appRevisionLimit+1 {
return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit+1, len(appRevisionList.Items))
}
revKey = types.NamespacedName{Namespace: namespace, Name: appName + "-v2"}
err = k8sClient.Get(ctx, revKey, deletedRevison)
if err == nil || !apierrors.IsNotFound(err) {
return fmt.Errorf("haven't clean up the revision-2")
}
if res, err := util.CheckAppRevision(appRevisionList.Items, []int{3, 4, 5, 6, 7, 8}); err != nil || !res {
return fmt.Errorf("appRevision collection mismatch")
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
})
It("Test clean up appDeployment using appRevision", func() {
appName := "app-4"
appKey := types.NamespacedName{Namespace: namespace, Name: appName}
app := getApp(appName, namespace, "normal-worker")
metav1.SetMetaDataAnnotation(&app.ObjectMeta, oam.AnnotationAppRollout, "true")
metav1.SetMetaDataAnnotation(&app.ObjectMeta, oam.AnnotationRollingComponent, "comp1")
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
checkApp := new(v1beta1.Application)
for i := 0; i < appRevisionLimit+1; i++ {
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
property := fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, i)
checkApp.Spec.Components[0].Properties = runtime.RawExtension{Raw: []byte(property)}
Expect(k8sClient.Update(ctx, checkApp)).Should(BeNil())
_, err := reconciler.Reconcile(ctrl.Request{NamespacedName: appKey})
Expect(err).Should(BeNil())
}
appContext := new(v1alpha2.ApplicationContext)
Expect(k8sClient.Get(ctx, appKey, appContext)).Should(util.NotFoundMatcher{})
listOpts := []client.ListOption{
client.InNamespace(namespace),
client.MatchingLabels{
oam.LabelAppName: appName,
},
}
appRevisionList := new(v1beta1.ApplicationRevisionList)
Eventually(func() error {
err := k8sClient.List(ctx, appRevisionList, listOpts...)
if err != nil {
return err
}
if len(appRevisionList.Items) != appRevisionLimit+1 {
return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit+1, len(appRevisionList.Items))
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
By("create new appRevision will remove appRevison1")
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
property := fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, 6)
checkApp.Spec.Components[0].Properties = runtime.RawExtension{Raw: []byte(property)}
Expect(k8sClient.Update(ctx, checkApp)).Should(BeNil())
_, err := reconciler.Reconcile(ctrl.Request{NamespacedName: appKey})
Expect(err).Should(BeNil())
deletedRevison := new(v1beta1.ApplicationRevision)
revKey := types.NamespacedName{Namespace: namespace, Name: appName + "-v1"}
Eventually(func() error {
err := k8sClient.List(ctx, appRevisionList, listOpts...)
if err != nil {
return err
}
if len(appRevisionList.Items) != appRevisionLimit+1 {
return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit+1, len(appRevisionList.Items))
}
err = k8sClient.Get(ctx, revKey, deletedRevison)
if err == nil || !apierrors.IsNotFound(err) {
return fmt.Errorf("haven't clean up the oldest revision")
}
if res, err := util.CheckAppRevision(appRevisionList.Items, []int{2, 3, 4, 5, 6, 7}); err != nil || !res {
return fmt.Errorf("appRevision collection mismatch")
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
By("update create appDeploy check gc logic")
appDeploy := &v1beta1.AppDeployment{
TypeMeta: metav1.TypeMeta{
APIVersion: v1beta1.AppDeploymentKindAPIVersion,
Kind: v1beta1.AppDeploymentKind,
},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "app-deploy",
},
Spec: v1beta1.AppDeploymentSpec{
AppRevisions: []v1beta1.AppRevision{
{
RevisionName: appName + "-v2",
},
},
},
}
Expect(k8sClient.Create(ctx, appDeploy)).Should(BeNil())
// give informer some time to cache
time.Sleep(2 * time.Second)
for i := 7; i < 9; i++ {
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
property = fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, i)
checkApp.Spec.Components[0].Properties = runtime.RawExtension{Raw: []byte(property)}
Expect(k8sClient.Update(ctx, checkApp)).Should(BeNil())
_, err = reconciler.Reconcile(ctrl.Request{NamespacedName: appKey})
Expect(err).Should(BeNil())
}
Eventually(func() error {
err := k8sClient.List(ctx, appRevisionList, listOpts...)
if err != nil {
return err
}
if len(appRevisionList.Items) != appRevisionLimit+2 {
return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit+2, len(appRevisionList.Items))
}
revKey = types.NamespacedName{Namespace: namespace, Name: appName + "-v3"}
err = k8sClient.Get(ctx, revKey, deletedRevison)
if err == nil || !apierrors.IsNotFound(err) {
return fmt.Errorf("haven't clean up the revision-3")
}
if res, err := util.CheckAppRevision(appRevisionList.Items, []int{2, 4, 5, 6, 7, 8, 9}); err != nil || !res {
return fmt.Errorf("appRevision collection mismatch")
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
})
})
var _ = Describe("Test gatherUsingAppRevision func", func() {
ctx := context.TODO()
namespace := "clean-up-revision"
cd := &v1beta1.ComponentDefinition{}
cdDefJson, _ := yaml.YAMLToJSON([]byte(normalCompDefYaml))
BeforeEach(func() {
ns := v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: namespace,
},
}
Expect(k8sClient.Create(ctx, &ns)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
Expect(json.Unmarshal(cdDefJson, cd)).Should(BeNil())
Expect(k8sClient.Create(ctx, cd.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
})
AfterEach(func() {
By("[TEST] Clean up resources after an integration test")
})
It("get gatherUsingAppRevision func logic", func() {
appName := "app-3"
app := getApp(appName, namespace, "normal-worker")
app.Status.LatestRevision = &common.Revision{
Name: appName + "-v1",
}
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
appContext := getAppContext(namespace, appName+"-ctx", appName+"-v2")
appContext.Labels = map[string]string{
oam.LabelAppName: appName,
}
Expect(k8sClient.Create(ctx, appContext)).Should(BeNil())
handler := appHandler{
r: reconciler,
app: app,
logger: reconciler.Log.WithValues("application", "gatherUsingAppRevision-func-test"),
}
Eventually(func() error {
using, err := gatherUsingAppRevision(ctx, &handler)
if err != nil {
return err
}
if len(using) != 2 {
return fmt.Errorf("wrong revision number")
}
if !using[appName+"-v1"] {
return fmt.Errorf("revison1 not include")
}
if !using[appName+"-v2"] {
return fmt.Errorf("revison2 not include")
}
return nil
}, time.Second*60, time.Microsecond).Should(BeNil())
})
})
func getAppContext(namespace, name string, pointingRev string) *v1alpha2.ApplicationContext {
return &v1alpha2.ApplicationContext{
TypeMeta: metav1.TypeMeta{
APIVersion: v1alpha2.ApplicationContextKindAPIVersion,
Kind: v1alpha2.ApplicationContextKind,
},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "app-rollout",
},
Spec: v1alpha2.ApplicationContextSpec{
ApplicationRevisionName: pointingRev,
},
}
}

View File

@@ -61,6 +61,7 @@ var testScheme = runtime.NewScheme()
var reconciler *Reconciler
var stop = make(chan struct{})
var ctlManager ctrl.Manager
var appRevisionLimit = 5
// TODO: create a mock client and add UT to cover all the failure cases
@@ -122,11 +123,12 @@ var _ = BeforeSuite(func(done Done) {
pd, err := definition.NewPackageDiscover(cfg)
Expect(err).To(BeNil())
reconciler = &Reconciler{
Client: k8sClient,
Log: ctrl.Log.WithName("Application-Test"),
Scheme: testScheme,
dm: dm,
pd: pd,
Client: k8sClient,
Log: ctrl.Log.WithName("Application-Test"),
Scheme: testScheme,
dm: dm,
pd: pd,
appRevisionLimit: appRevisionLimit,
}
// setup the controller manager since we need the component handler to run in the background
ctlManager, err = ctrl.NewManager(cfg, ctrl.Options{

View File

@@ -294,3 +294,45 @@ func RefreshPackageDiscover(dm discoverymapper.DiscoveryMapper, pd *definition.P
}
return nil
}
// CheckAppDeploymentUsingAppRevision get all appDeployments using appRevisions related the app
func CheckAppDeploymentUsingAppRevision(ctx context.Context, c client.Reader, appNs string, appName string) ([]string, error) {
deployOpts := []client.ListOption{
client.InNamespace(appNs),
}
var res []string
ads := new(v1beta1.AppDeploymentList)
if err := c.List(ctx, ads, deployOpts...); err != nil {
return nil, err
}
if len(ads.Items) == 0 {
return res, nil
}
relatedRevs := new(v1beta1.ApplicationRevisionList)
revOpts := []client.ListOption{
client.InNamespace(appNs),
client.MatchingLabels{oam.LabelAppName: appName},
}
if err := c.List(ctx, relatedRevs, revOpts...); err != nil {
return nil, err
}
if len(relatedRevs.Items) == 0 {
return res, nil
}
revName := map[string]bool{}
for _, rev := range relatedRevs.Items {
if len(rev.Name) != 0 {
revName[rev.Name] = true
}
}
for _, d := range ads.Items {
for _, dr := range d.Spec.AppRevisions {
if len(dr.RevisionName) != 0 {
if revName[dr.RevisionName] {
res = append(res, dr.RevisionName)
}
}
}
}
return res, nil
}

View File

@@ -18,6 +18,8 @@ package util
import (
"encoding/json"
"reflect"
"sort"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
@@ -187,3 +189,24 @@ func UnMarshalStringToTraitDefinition(s string) (*v1beta1.TraitDefinition, error
}
return obj, nil
}
// CheckAppRevision check if appRevision list is right
func CheckAppRevision(revs []v1beta1.ApplicationRevision, collection []int) (bool, error) {
if len(revs) != len(collection) {
return false, nil
}
var revNums []int
for _, rev := range revs {
num, err := ExtractRevisionNum(rev.Name)
if err != nil {
return false, err
}
revNums = append(revNums, num)
}
sort.Ints(revNums)
sort.Ints(collection)
if reflect.DeepEqual(revNums, collection) {
return true, nil
}
return false, nil
}

View File

@@ -20,6 +20,10 @@ import (
"fmt"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/errors"
@@ -219,3 +223,125 @@ func TestErrorMatcher(t *testing.T) {
assert.Equal(t, tc.want.negatedFailureMessage, matcher.NegatedFailureMessage(tc.input.input))
}
}
func TestCheckAppRevision(t *testing.T) {
testcases := map[string]struct {
revs []v1beta1.ApplicationRevision
collection []int
want bool
hasError bool
}{
"match": {
revs: []v1beta1.ApplicationRevision{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-v1",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-v2",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-v3",
},
},
},
collection: []int{1, 2, 3},
want: true,
hasError: false,
},
"lengthNotMatch": {
revs: []v1beta1.ApplicationRevision{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-v1",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-v2",
},
},
},
collection: []int{1, 2, 3},
want: false,
hasError: false,
},
"notMatch": {
revs: []v1beta1.ApplicationRevision{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-v1",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-v2",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-v4",
},
},
},
collection: []int{1, 2, 3},
want: false,
hasError: false,
},
"testSort": {
revs: []v1beta1.ApplicationRevision{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-v1",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-v3",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-v2",
},
},
},
collection: []int{3, 2, 1},
want: true,
hasError: false,
},
"testErrorName": {
revs: []v1beta1.ApplicationRevision{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-v1",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-v3",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "app-vs",
},
},
},
collection: []int{3, 2, 1},
want: false,
hasError: true,
},
}
for name, testcase := range testcases {
t.Log(fmt.Sprint("Running test: ", name))
checkEqual, err := CheckAppRevision(testcase.revs, testcase.collection)
hasError := err != nil
assert.Equal(t, checkEqual, testcase.want)
assert.Equal(t, hasError, testcase.hasError)
}
}

View File

@@ -0,0 +1,432 @@
/*
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 controllers_test
import (
"context"
"encoding/json"
"fmt"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/ghodss/yaml"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/oam/util"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client"
)
var (
appRevisionLimit = 5
)
var _ = Describe("Test application controller clean up appRevision", func() {
ctx := context.TODO()
namespace := "clean-up-revision"
cd := &v1beta1.ComponentDefinition{}
cdDefJson, _ := yaml.YAMLToJSON([]byte(compDefYaml))
BeforeEach(func() {
ns := v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: namespace,
},
}
Expect(k8sClient.Create(ctx, &ns)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
Expect(json.Unmarshal(cdDefJson, cd)).Should(BeNil())
Expect(k8sClient.Create(ctx, cd.DeepCopy())).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
})
AfterEach(func() {
By("[TEST] Clean up resources after an integration test")
Expect(k8sClient.Delete(ctx, &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}, client.PropagationPolicy(metav1.DeletePropagationForeground))).Should(Succeed())
// guarantee namespace have been deleted
Eventually(func() error {
ns := new(v1.Namespace)
err := k8sClient.Get(ctx, types.NamespacedName{Name: namespace}, ns)
if err == nil {
return fmt.Errorf("namespace still exist")
}
return nil
}, time.Second*60, time.Microsecond*300).Should(BeNil())
})
It("Test clean up appRevision", func() {
appName := "app-1"
appKey := types.NamespacedName{Namespace: namespace, Name: appName}
app := getApp(appName, namespace, "normal-worker")
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
checkApp := new(v1beta1.Application)
for i := 0; i < appRevisionLimit+1; i++ {
Eventually(func() error {
checkApp = new(v1beta1.Application)
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
if checkApp.Status.LatestRevision == nil || checkApp.Status.LatestRevision.Revision != int64(i+1) {
return fmt.Errorf("application point to wrong revision")
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
Eventually(func() error {
checkApp = new(v1beta1.Application)
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
property := fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, i)
checkApp.Spec.Components[0].Properties = runtime.RawExtension{Raw: []byte(property)}
if err := k8sClient.Update(ctx, checkApp); err != nil {
return err
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
}
appContext := new(v1alpha2.ApplicationContext)
Expect(k8sClient.Get(ctx, appKey, appContext)).Should(BeNil())
listOpts := []client.ListOption{
client.InNamespace(namespace),
client.MatchingLabels{
oam.LabelAppName: appName,
},
}
appRevisionList := new(v1beta1.ApplicationRevisionList)
Eventually(func() error {
err := k8sClient.List(ctx, appRevisionList, listOpts...)
if err != nil {
return err
}
if len(appRevisionList.Items) != appRevisionLimit+1 {
return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit+1, len(appRevisionList.Items))
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
Eventually(func() error {
if err := k8sClient.Get(ctx, appKey, appContext); err != nil {
return err
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
By("create new appRevision will remove appRevison1")
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
property := fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, 6)
checkApp.Spec.Components[0].Properties = runtime.RawExtension{Raw: []byte(property)}
Expect(k8sClient.Update(ctx, checkApp)).Should(BeNil())
deletedRevison := new(v1beta1.ApplicationRevision)
revKey := types.NamespacedName{Namespace: namespace, Name: appName + "-v1"}
Eventually(func() error {
err := k8sClient.List(ctx, appRevisionList, listOpts...)
if err != nil {
return err
}
if len(appRevisionList.Items) != appRevisionLimit+1 {
return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit+1, len(appRevisionList.Items))
}
err = k8sClient.Get(ctx, revKey, deletedRevison)
if err == nil || !apierrors.IsNotFound(err) {
return fmt.Errorf("haven't clean up the oldest revision")
}
if res, err := util.CheckAppRevision(appRevisionList.Items, []int{2, 3, 4, 5, 6, 7}); err != nil || !res {
return fmt.Errorf("appRevision collection mismatch")
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
By("update app again will gc appRevision2")
Eventually(func() error {
if err := k8sClient.Get(ctx, appKey, checkApp); err != nil {
return err
}
property = fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, 7)
checkApp.Spec.Components[0].Properties = runtime.RawExtension{Raw: []byte(property)}
if err := k8sClient.Update(ctx, checkApp); err != nil {
return err
}
return nil
})
Eventually(func() error {
err := k8sClient.List(ctx, appRevisionList, listOpts...)
if err != nil {
return err
}
if len(appRevisionList.Items) != appRevisionLimit+1 {
return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit+1, len(appRevisionList.Items))
}
revKey = types.NamespacedName{Namespace: namespace, Name: appName + "-v2"}
err = k8sClient.Get(ctx, revKey, deletedRevison)
if err == nil || !apierrors.IsNotFound(err) {
return fmt.Errorf("haven't clean up the revision-2")
}
if res, err := util.CheckAppRevision(appRevisionList.Items, []int{3, 4, 5, 6, 7, 8}); err != nil || !res {
return fmt.Errorf("appRevision collection mismatch")
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
})
It("Test clean up rollout appRevision", func() {
appName := "app-2"
appKey := types.NamespacedName{Namespace: namespace, Name: appName}
app := getApp(appName, namespace, "normal-worker")
metav1.SetMetaDataAnnotation(&app.ObjectMeta, oam.AnnotationAppRollout, "true")
metav1.SetMetaDataAnnotation(&app.ObjectMeta, oam.AnnotationRollingComponent, "comp1")
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
checkApp := new(v1beta1.Application)
for i := 0; i < appRevisionLimit; i++ {
Eventually(func() error {
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
if checkApp.Status.LatestRevision == nil || checkApp.Status.LatestRevision.Revision != int64(i+1) {
return fmt.Errorf("application point to wrong revision")
}
return nil
}, time.Second*30, time.Microsecond).Should(BeNil())
Eventually(func() error {
checkApp = new(v1beta1.Application)
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
property := fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, i)
checkApp.Spec.Components[0].Properties = runtime.RawExtension{Raw: []byte(property)}
if err := k8sClient.Update(ctx, checkApp); err != nil {
return err
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
}
appContext := new(v1alpha2.ApplicationContext)
Expect(k8sClient.Get(ctx, appKey, appContext)).Should(util.NotFoundMatcher{})
listOpts := []client.ListOption{
client.InNamespace(namespace),
client.MatchingLabels{
oam.LabelAppName: appName,
},
}
appRevisionList := new(v1beta1.ApplicationRevisionList)
Eventually(func() error {
err := k8sClient.List(ctx, appRevisionList, listOpts...)
if err != nil {
return err
}
if len(appRevisionList.Items) != appRevisionLimit+1 {
return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit+1, len(appRevisionList.Items))
}
return nil
}, time.Second*300, time.Microsecond*300).Should(BeNil())
By("create new appRevision will remove appRevison1")
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
property := fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, 5)
checkApp.Spec.Components[0].Properties = runtime.RawExtension{Raw: []byte(property)}
Expect(k8sClient.Update(ctx, checkApp)).Should(BeNil())
deletedRevison := new(v1beta1.ApplicationRevision)
revKey := types.NamespacedName{Namespace: namespace, Name: appName + "-v1"}
Eventually(func() error {
err := k8sClient.List(ctx, appRevisionList, listOpts...)
if err != nil {
return err
}
if len(appRevisionList.Items) != appRevisionLimit+1 {
return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit, len(appRevisionList.Items))
}
err = k8sClient.Get(ctx, revKey, deletedRevison)
if err == nil || !apierrors.IsNotFound(err) {
return fmt.Errorf("haven't clean up the oldest revision")
}
if res, err := util.CheckAppRevision(appRevisionList.Items, []int{2, 3, 4, 5, 6, 7}); err != nil || !res {
return fmt.Errorf("appRevision collection mismatch")
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
By("update app again will gc appRevision2")
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
property = fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, 6)
checkApp.Spec.Components[0].Properties = runtime.RawExtension{Raw: []byte(property)}
Expect(k8sClient.Update(ctx, checkApp)).Should(BeNil())
Eventually(func() error {
err := k8sClient.List(ctx, appRevisionList, listOpts...)
if err != nil {
return err
}
if len(appRevisionList.Items) != appRevisionLimit+1 {
return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit, len(appRevisionList.Items))
}
revKey = types.NamespacedName{Namespace: namespace, Name: appName + "-v2"}
err = k8sClient.Get(ctx, revKey, deletedRevison)
if err == nil || !apierrors.IsNotFound(err) {
return fmt.Errorf("haven't clean up the revision-2")
}
if res, err := util.CheckAppRevision(appRevisionList.Items, []int{3, 4, 5, 6, 7, 8}); err != nil || !res {
return fmt.Errorf("appRevision collection mismatch")
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
appRollout := &v1beta1.AppRollout{
TypeMeta: metav1.TypeMeta{
APIVersion: v1beta1.AppRolloutKindAPIVersion,
Kind: v1beta1.AppRolloutKind,
},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "app-roll-out",
},
Spec: v1beta1.AppRolloutSpec{
TargetAppRevisionName: appName + "-v3",
ComponentList: []string{"comp1"},
RolloutPlan: v1alpha1.RolloutPlan{
RolloutBatches: []v1alpha1.RolloutBatch{
{
Replicas: intstr.FromInt(1),
},
},
},
},
}
Expect(k8sClient.Create(ctx, appRollout)).Should(BeNil())
By("update app twice will gc appRevision4 not appRevision3")
for i := 7; i < 9; i++ {
Eventually(func() error {
if err := k8sClient.Get(ctx, appKey, checkApp); err != nil {
return err
}
if checkApp.Status.LatestRevision == nil || checkApp.Status.LatestRevision.Revision != int64(i+1) {
return fmt.Errorf("application point to wrong revision")
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
Eventually(func() error {
if err := k8sClient.Get(ctx, appKey, checkApp); err != nil {
return err
}
property = fmt.Sprintf(`{"cmd":["sleep","1000"],"image":"busybox:%d"}`, i)
checkApp.Spec.Components[0].Properties = runtime.RawExtension{Raw: []byte(property)}
if err := k8sClient.Update(ctx, checkApp); err != nil {
return err
}
return nil
}, time.Second*30, time.Microsecond).Should(BeNil())
}
Eventually(func() error {
err := k8sClient.List(ctx, appRevisionList, listOpts...)
if err != nil {
return err
}
if len(appRevisionList.Items) != appRevisionLimit+2 {
return fmt.Errorf("error appRevison number wants %d, actually %d", appRevisionLimit+2, len(appRevisionList.Items))
}
revKey = types.NamespacedName{Namespace: namespace, Name: appName + "-v4"}
err = k8sClient.Get(ctx, revKey, deletedRevison)
if err == nil || !apierrors.IsNotFound(err) {
return fmt.Errorf("haven't clean up the revision-4")
}
existRev := new(v1beta1.ApplicationRevision)
revKey = types.NamespacedName{Namespace: namespace, Name: appName + "-v3"}
err = k8sClient.Get(ctx, revKey, existRev)
if err != nil {
return err
}
if res, err := util.CheckAppRevision(appRevisionList.Items, []int{3, 5, 6, 7, 8, 9, 10}); err != nil || !res {
return fmt.Errorf("appRevision collection mismatch")
}
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
})
})
var (
compDefYaml = `
apiVersion: core.oam.dev/v1beta1
kind: ComponentDefinition
metadata:
name: normal-worker
namespace: clean-up-revision
annotations:
definition.oam.dev/description: "Long-running scalable backend worker without network endpoint"
spec:
workload:
definition:
apiVersion: apps/v1
kind: Deployment
extension:
healthPolicy: |
isHealth: context.output.status.readyReplicas == context.output.status.replicas
template: |
output: {
apiVersion: "apps/v1"
kind: "Deployment"
spec: {
replicas: 0
template: {
metadata: labels: {
"app.oam.dev/component": context.name
}
spec: {
containers: [{
name: context.name
image: parameter.image
if parameter["cmd"] != _|_ {
command: parameter.cmd
}
}]
}
}
selector:
matchLabels:
"app.oam.dev/component": context.name
}
}
parameter: {
// +usage=Which image would you like to use for your service
// +short=i
image: string
cmd?: [...string]
}
`
)
func getApp(appName, namespace, comptype string) *v1beta1.Application {
return &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: appName,
Namespace: namespace,
},
Spec: v1beta1.ApplicationSpec{
Components: []v1beta1.ApplicationComponent{
{
Name: "comp1",
Type: comptype,
Properties: runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
},
},
},
}
}