garbage collection for cross-namespace workloads and traits in application layer (#1421)

* add logic for gc cross ns resources

WIP regenerate api and charts

rewrite handle resourceTracker logic

WIP add e2e test logic

add more e2e  test

* WIP refactor sevral funs name and add comments for code

* WIP add more corener case e2e test

* add unit test and fix bug

* WIP refactor handleResouceTracker  logic

* refactor func name aglin with others and add comments

* make generate to fix check-diff ci error

* change resourceTracker status as a subresource
This commit is contained in:
wyike
2021-04-07 21:34:48 +08:00
committed by GitHub
parent 15b1bd2660
commit b33b6fbead
9 changed files with 947 additions and 134 deletions
+30
View File
@@ -20,6 +20,7 @@ import (
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
)
@@ -236,12 +237,41 @@ type ScopeDefinitionList struct {
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// An ResourceTracker represents a tracker for track cross namespace resources
// +kubebuilder:resource:scope=Cluster,categories={oam}
type ResourceTracker struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Status ResourceTrackerStatus `json:"status,omitempty"`
}
// ResourceTrackerStatus define the status of resourceTracker
type ResourceTrackerStatus struct {
TrackedResources []TypedReference `json:"trackedResources,omitempty"`
}
// A TypedReference refers to an object by Name, Kind, and APIVersion. It is
// commonly used to reference across-namespace objects
type TypedReference struct {
// APIVersion of the referenced object.
APIVersion string `json:"apiVersion"`
// Kind of the referenced object.
Kind string `json:"kind"`
// Name of the referenced object.
Name string `json:"name"`
// Namespace of the objects outside the application namespace.
// +optional
Namespace string `json:"namespace,omitempty"`
// UID of the referenced object.
// +optional
UID types.UID `json:"uid,omitempty"`
}
// +kubebuilder:object:root=true
@@ -862,6 +862,7 @@ func (in *ResourceTracker) DeepCopyInto(out *ResourceTracker) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceTracker.
@@ -914,6 +915,26 @@ func (in *ResourceTrackerList) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ResourceTrackerStatus) DeepCopyInto(out *ResourceTrackerStatus) {
*out = *in
if in.TrackedResources != nil {
in, out := &in.TrackedResources, &out.TrackedResources
*out = make([]TypedReference, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceTrackerStatus.
func (in *ResourceTrackerStatus) DeepCopy() *ResourceTrackerStatus {
if in == nil {
return nil
}
out := new(ResourceTrackerStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ScopeDefinition) DeepCopyInto(out *ScopeDefinition) {
*out = *in
@@ -1141,6 +1162,21 @@ func (in *TraitDefinitionStatus) DeepCopy() *TraitDefinitionStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TypedReference) DeepCopyInto(out *TypedReference) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TypedReference.
func (in *TypedReference) DeepCopy() *TypedReference {
if in == nil {
return nil
}
out := new(TypedReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *URIMatch) DeepCopyInto(out *URIMatch) {
*out = *in
@@ -30,9 +30,40 @@ spec:
type: string
metadata:
type: object
status:
description: ResourceTrackerStatus define the status of resourceTracker
properties:
trackedResources:
items:
description: A TypedReference refers to an object by Name, Kind, and APIVersion. It is commonly used to reference across-namespace objects
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
namespace:
description: Namespace of the objects outside the application namespace.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
type: array
type: object
type: object
served: true
storage: true
subresources:
status: {}
status:
acceptedNames:
kind: ""
-3
View File
@@ -50,7 +50,4 @@ spec:
}}}
}
```
## Limitations
If you update definition by changing the `metadata.namespace` field. KubeVela will create new resources in the new namespace but not delete old resources.
We wil fix the limitation in the near future.
@@ -16,6 +16,8 @@ spec:
plural: resourcetrackers
singular: resourcetracker
scope: Cluster
subresources:
status: {}
validation:
openAPIV3Schema:
description: An ResourceTracker represents a tracker for track cross namespace resources
@@ -28,6 +30,35 @@ spec:
type: string
metadata:
type: object
status:
description: ResourceTrackerStatus define the status of resourceTracker
properties:
trackedResources:
items:
description: A TypedReference refers to an object by Name, Kind, and APIVersion. It is commonly used to reference across-namespace objects
properties:
apiVersion:
description: APIVersion of the referenced object.
type: string
kind:
description: Kind of the referenced object.
type: string
name:
description: Name of the referenced object.
type: string
namespace:
description: Namespace of the objects outside the application namespace.
type: string
uid:
description: UID of the referenced object.
type: string
required:
- apiVersion
- kind
- name
type: object
type: array
type: object
type: object
version: v1beta1
versions:
@@ -145,6 +145,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
err = handler.handleResourceTracker(ctx, comps, ac)
if err != nil {
applog.Error(err, "[Handle resourceTracker]")
app.Status.SetConditions(errorCondition("Handle resourceTracker", err))
return handler.handleErr(err)
}
@@ -180,6 +181,12 @@ 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)
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
for _, comp := range comps {
@@ -166,6 +166,88 @@ var _ = Describe("Test application controller finalizer logic", func() {
checkRt := new(v1beta1.ResourceTracker)
Expect(k8sClient.Get(ctx, getTrackerKey(checkApp.Namespace, checkApp.Name), checkRt)).Should(util.NotFoundMatcher{})
})
It("Test cross namespace workload, then update the app to change the namespace", func() {
appName := "app-3"
appKey := types.NamespacedName{Namespace: namespace, Name: appName}
app := getApp(appName, namespace, "cross-worker")
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
By("Create a cross workload app")
_, err := reconciler.Reconcile(ctrl.Request{NamespacedName: appKey})
Expect(err).Should(BeNil())
checkApp := &v1beta1.Application{}
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
Expect(checkApp.Status.Phase).Should(Equal(common.ApplicationRunning))
Expect(len(checkApp.Finalizers)).Should(BeEquivalentTo(0))
rt := &v1beta1.ResourceTracker{}
Expect(k8sClient.Get(ctx, getTrackerKey(checkApp.Namespace, checkApp.Name), rt)).Should(BeNil())
_, err = reconciler.Reconcile(ctrl.Request{NamespacedName: appKey})
Expect(err).Should(BeNil())
checkApp = new(v1beta1.Application)
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
Expect(len(checkApp.Finalizers)).Should(BeEquivalentTo(1))
Expect(checkApp.Finalizers[0]).Should(BeEquivalentTo(resourceTrackerFinalizer))
Expect(checkApp.Status.ResourceTracker.UID).Should(BeEquivalentTo(rt.UID))
Expect(len(rt.Status.TrackedResources)).Should(BeEquivalentTo(1))
By("Update the app, set type to normal-worker")
checkApp.Spec.Components[0].Type = "normal-worker"
Expect(k8sClient.Update(ctx, checkApp)).Should(BeNil())
_, err = reconciler.Reconcile(ctrl.Request{NamespacedName: appKey})
Expect(err).Should(BeNil())
checkApp = new(v1beta1.Application)
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
Expect(checkApp.Status.ResourceTracker).Should(BeNil())
Expect(k8sClient.Get(ctx, getTrackerKey(checkApp.Namespace, checkApp.Name), rt)).Should(util.NotFoundMatcher{})
Expect(k8sClient.Delete(ctx, checkApp)).Should(BeNil())
_, err = reconciler.Reconcile(ctrl.Request{NamespacedName: appKey})
Expect(err).Should(BeNil())
})
It("Test cross namespace workload and trait, then update the app to delete trait ", func() {
appName := "app-4"
appKey := types.NamespacedName{Namespace: namespace, Name: appName}
app := getApp(appName, namespace, "cross-worker")
app.Spec.Components[0].Traits = []v1beta1.ApplicationTrait{
{
Type: "cross-scaler",
Properties: runtime.RawExtension{Raw: []byte(`{"replicas": 1}`)},
},
}
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
By("Create a cross workload trait app")
_, err := reconciler.Reconcile(ctrl.Request{NamespacedName: appKey})
Expect(err).Should(BeNil())
checkApp := &v1beta1.Application{}
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
Expect(checkApp.Status.Phase).Should(Equal(common.ApplicationRunning))
Expect(len(checkApp.Finalizers)).Should(BeEquivalentTo(0))
rt := &v1beta1.ResourceTracker{}
Expect(k8sClient.Get(ctx, getTrackerKey(checkApp.Namespace, checkApp.Name), rt)).Should(BeNil())
_, err = reconciler.Reconcile(ctrl.Request{NamespacedName: appKey})
Expect(err).Should(BeNil())
checkApp = new(v1beta1.Application)
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
Expect(len(checkApp.Finalizers)).Should(BeEquivalentTo(1))
Expect(checkApp.Finalizers[0]).Should(BeEquivalentTo(resourceTrackerFinalizer))
Expect(checkApp.Status.ResourceTracker.UID).Should(BeEquivalentTo(rt.UID))
Expect(len(rt.Status.TrackedResources)).Should(BeEquivalentTo(2))
By("Update the app, set type to normal-worker")
checkApp.Spec.Components[0].Traits = nil
Expect(k8sClient.Update(ctx, checkApp)).Should(BeNil())
_, err = reconciler.Reconcile(ctrl.Request{NamespacedName: appKey})
Expect(err).Should(BeNil())
rt = &v1beta1.ResourceTracker{}
checkApp = new(v1beta1.Application)
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
Expect(k8sClient.Get(ctx, getTrackerKey(checkApp.Namespace, checkApp.Name), rt)).Should(BeNil())
Expect(checkApp.Status.ResourceTracker.UID).Should(BeEquivalentTo(rt.UID))
Expect(len(rt.Status.TrackedResources)).Should(BeEquivalentTo(1))
Expect(k8sClient.Delete(ctx, checkApp)).Should(BeNil())
_, err = reconciler.Reconcile(ctrl.Request{NamespacedName: appKey})
Expect(err).Should(BeNil())
Expect(k8sClient.Get(ctx, getTrackerKey(checkApp.Namespace, checkApp.Name), rt)).Should(util.NotFoundMatcher{})
})
})
var _ = Describe("Test finalizer related func", func() {
@@ -186,46 +268,6 @@ var _ = Describe("Test finalizer related func", func() {
By("[TEST] Clean up resources after an integration test")
})
It("Test getResourceTrackerAndOwnReference func", func() {
app := getApp("app-1", namespace, "worker")
handler = appHandler{
r: reconciler,
app: app,
logger: reconciler.Log.WithValues("application", "finalizer-func-test"),
}
checkRt := new(v1beta1.ResourceTracker)
Expect(k8sClient.Get(ctx, getTrackerKey(namespace, app.Name), checkRt)).Should(util.NotFoundMatcher{})
rt, owner, err := handler.getResourceTrackerAndOwnReference(ctx)
Expect(err).Should(BeNil())
Expect(rt.UID).Should(BeEquivalentTo(owner.UID))
Expect(owner.Kind).Should(BeEquivalentTo(v1beta1.ResourceTrackerKind))
checkRt = new(v1beta1.ResourceTracker)
Expect(k8sClient.Get(ctx, getTrackerKey(namespace, app.Name), checkRt)).Should(BeNil())
Expect(checkRt.UID).Should(BeEquivalentTo(rt.UID))
Expect(k8sClient.Delete(ctx, checkRt)).Should(BeNil())
})
It("Test getResourceTrackerAndOwnReference func with already exsit resourceTracker", func() {
app := getApp("app-2", namespace, "worker")
handler = appHandler{
r: reconciler,
app: app,
logger: reconciler.Log.WithValues("application", "finalizer-func-test"),
}
rt := &v1beta1.ResourceTracker{
ObjectMeta: metav1.ObjectMeta{
Name: namespace + "-" + app.GetName(),
},
}
Expect(k8sClient.Create(ctx, rt)).Should(BeNil())
checkRt, owner, err := handler.getResourceTrackerAndOwnReference(ctx)
Expect(err).Should(BeNil())
Expect(rt.UID).Should(BeEquivalentTo(checkRt.UID))
Expect(owner.Kind).Should(BeEquivalentTo(v1beta1.ResourceTrackerKind))
Expect(checkRt.UID).Should(BeEquivalentTo(owner.UID))
Expect(k8sClient.Delete(ctx, checkRt)).Should(BeNil())
})
It("Test finalizeResourceTracker func with need update ", func() {
app := getApp("app-3", namespace, "worker")
rt := &v1beta1.ResourceTracker{
@@ -20,6 +20,7 @@ import (
"context"
"fmt"
"strconv"
"strings"
"time"
runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
@@ -31,6 +32,7 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
ctypes "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/klog/v2"
@@ -41,6 +43,7 @@ import (
"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/appfile"
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration"
"github.com/oam-dev/kubevela/pkg/controller/utils"
"github.com/oam-dev/kubevela/pkg/dsl/process"
"github.com/oam-dev/kubevela/pkg/oam"
@@ -67,13 +70,15 @@ func readyCondition(tpy string) runtimev1alpha1.Condition {
}
type appHandler struct {
r *Reconciler
app *v1beta1.Application
appfile *appfile.Appfile
logger logr.Logger
inplace bool
isNewRevision bool
revisionHash string
r *Reconciler
app *v1beta1.Application
appfile *appfile.Appfile
logger logr.Logger
inplace bool
isNewRevision bool
revisionHash string
acrossNamespaceResources []v1beta1.TypedReference
resourceTracker *v1beta1.ResourceTracker
}
// setInplace will mark if the application should upgrade the workload within the same instance(name never changed)
@@ -108,6 +113,12 @@ func (h *appHandler) apply(ctx context.Context, appRev *v1beta1.ApplicationRevis
Controller: pointer.BoolPtr(true),
}}
if _, exist := h.app.GetAnnotations()[oam.AnnotationAppRollout]; !exist && h.app.Spec.RolloutPlan == nil {
h.setInplace(true)
} else {
h.setInplace(false)
}
// don't create components and AC if revision-only annotation is set
if ac.Annotations[oam.AnnotationAppRevisionOnly] == "true" {
h.FinalizeAppRevision(appRev, ac, comps)
@@ -116,12 +127,21 @@ func (h *appHandler) apply(ctx context.Context, appRev *v1beta1.ApplicationRevis
for _, comp := range comps {
comp.SetOwnerReferences(owners)
needTracker, err := h.checkAndSetResourceTracker(&comp.Spec.Workload)
if err != nil {
return err
}
newComp := comp.DeepCopy()
// newComp will be updated and return the revision name instead of the component name
revisionName, err := h.createOrUpdateComponent(ctx, newComp)
if err != nil {
return err
}
if needTracker {
if err := h.recodeTrackedWorkload(comp, revisionName); err != nil {
return err
}
}
// find the ACC that contains this component
for i := 0; i < len(ac.Spec.Components); i++ {
// update the AC using the component revision instead of component name
@@ -129,6 +149,9 @@ func (h *appHandler) apply(ctx context.Context, appRev *v1beta1.ApplicationRevis
if ac.Spec.Components[i].ComponentName == newComp.Name {
ac.Spec.Components[i].RevisionName = revisionName
ac.Spec.Components[i].ComponentName = ""
if err := h.checkResourceTrackerForTrait(ctx, ac.Spec.Components[i], newComp.Name); err != nil {
return err
}
}
}
if comp.Spec.Helm != nil {
@@ -148,11 +171,9 @@ func (h *appHandler) apply(ctx context.Context, appRev *v1beta1.ApplicationRevis
// the rollout will create AppContext which will launch the real K8s resources.
// Otherwise, we should create/update the appContext here when there if no rollout controller to take care of new versions
// In this case, the workload should update with the annotation `app.oam.dev/inplace-upgrade=true`
if _, exist := h.app.GetAnnotations()[oam.AnnotationAppRollout]; !exist && h.app.Spec.RolloutPlan == nil {
h.setInplace(true)
if h.inplace {
return h.createOrUpdateAppContext(ctx, owners)
}
h.setInplace(false)
return nil
}
@@ -399,90 +420,30 @@ func (h *appHandler) applyHelmModuleResources(ctx context.Context, comp *v1alpha
return nil
}
// handleResourceTracker check the namesapce of all components and traits, if the namespace is different with application, the tracker will own them
func (h *appHandler) handleResourceTracker(ctx context.Context, components []*v1alpha2.Component, ac *v1alpha2.ApplicationConfiguration) error {
ref := new(metav1.OwnerReference)
// resourceTracker is cache for resourceTracker, avoid get from k8s every time
resourceTracker := new(v1beta1.ResourceTracker)
// checkAndSetResourceTracker check if resource's namespace is different with application, if yes set resourceTracker as
// resource's ownerReference
func (h *appHandler) checkAndSetResourceTracker(resource *runtime.RawExtension) (bool, error) {
needTracker := false
for i, c := range components {
u, err := oamutil.RawExtension2Unstructured(&c.Spec.Workload)
if err != nil {
return err
}
if checkResourceDiffWithApp(u, h.app.Namespace) {
needTracker = true
if len(resourceTracker.Name) == 0 {
resourceTracker, ref, err = h.getResourceTrackerAndOwnReference(ctx)
if err != nil {
return err
}
}
u.SetOwnerReferences([]metav1.OwnerReference{*ref})
raw := oamutil.Object2RawExtension(u)
components[i].Spec.Workload = raw
}
u, err := oamutil.RawExtension2Unstructured(resource)
if err != nil {
return false, err
}
for _, acComponent := range ac.Spec.Components {
for i, t := range acComponent.Traits {
u, err := oamutil.RawExtension2Unstructured(&t.Trait)
if err != nil {
return err
}
if checkResourceDiffWithApp(u, h.app.Namespace) {
needTracker = true
if len(resourceTracker.Name) == 0 {
resourceTracker, ref, err = h.getResourceTrackerAndOwnReference(ctx)
if err != nil {
return err
}
}
u.SetOwnerReferences([]metav1.OwnerReference{*ref})
raw := oamutil.Object2RawExtension(u)
acComponent.Traits[i].Trait = raw
}
}
if checkResourceDiffWithApp(u, h.app.Namespace) {
needTracker = true
ref := h.genResourceTrackerOwnerReference()
// set resourceTracker as the ownerReference of workload/trait
u.SetOwnerReferences([]metav1.OwnerReference{*ref})
raw := oamutil.Object2RawExtension(u)
*resource = raw
return needTracker, nil
}
if !needTracker {
h.app.Status.ResourceTracker = nil
// check weather related resourceTracker is existed, if yes delete it
err := h.r.Get(ctx, ctypes.NamespacedName{Name: h.generateResourceTrackerName()}, resourceTracker)
if err == nil {
return h.r.Delete(ctx, resourceTracker)
}
if !apierrors.IsNotFound(err) {
return err
}
return nil
}
h.app.Status.ResourceTracker = &runtimev1alpha1.TypedReference{
Name: resourceTracker.Name,
Kind: v1beta1.ResourceTrackerGroupKind,
APIVersion: v1beta1.ResourceTrackerKindAPIVersion,
UID: resourceTracker.UID}
return nil
return needTracker, nil
}
func (h *appHandler) getResourceTrackerAndOwnReference(ctx context.Context) (*v1beta1.ResourceTracker, *metav1.OwnerReference, error) {
resourceTracker := new(v1beta1.ResourceTracker)
key := ctypes.NamespacedName{Name: h.generateResourceTrackerName()}
err := h.r.Get(ctx, key, resourceTracker)
if err != nil {
if apierrors.IsNotFound(err) {
resourceTracker = &v1beta1.ResourceTracker{
ObjectMeta: metav1.ObjectMeta{
Name: h.generateResourceTrackerName(),
},
}
if err = h.r.Client.Create(ctx, resourceTracker); err != nil {
return nil, nil, err
}
return resourceTracker, metav1.NewControllerRef(resourceTracker, v1beta1.ResourceTrackerKindVersionKind), nil
}
return nil, nil, err
}
return resourceTracker, metav1.NewControllerRef(resourceTracker, v1beta1.ResourceTrackerKindVersionKind), nil
// genResourceTrackerOwnerReference check the related resourceTracker whether have been created.
// If not, create it. And return the ownerReference of this resourceTracker.
func (h *appHandler) genResourceTrackerOwnerReference() *metav1.OwnerReference {
return metav1.NewControllerRef(h.resourceTracker, v1beta1.ResourceTrackerKindVersionKind)
}
func (h *appHandler) generateResourceTrackerName() string {
@@ -529,3 +490,196 @@ func (h *appHandler) removeResourceTracker(ctx context.Context) (bool, error) {
h.app.Status.ResourceTracker = nil
return true, nil
}
func (h *appHandler) recodeTrackedWorkload(comp *v1alpha2.Component, compRevisionName string) error {
workloadName, err := h.getWorkloadName(comp.Spec.Workload, comp.Name, compRevisionName)
if err != nil {
return err
}
if err = h.recodeTrackedResource(workloadName, comp.Spec.Workload); err != nil {
return err
}
return nil
}
// checkResourceTrackerForTrait check component trait namespace, if it's namespace is different with application, set resourceTracker as its ownerReference
// and recode trait in handler acrossNamespace field
func (h *appHandler) checkResourceTrackerForTrait(ctx context.Context, comp v1alpha2.ApplicationConfigurationComponent, compName string) error {
for i, ct := range comp.Traits {
needTracker, err := h.checkAndSetResourceTracker(&comp.Traits[i].Trait)
if err != nil {
return err
}
if needTracker {
traitName, err := h.getTraitName(ctx, compName, comp.Traits[i].DeepCopy(), &ct.Trait)
if err != nil {
return err
}
if err = h.recodeTrackedResource(traitName, ct.Trait); err != nil {
return err
}
}
}
return nil
}
// getWorkloadName generate workload name. By default the workload's name will be generated by applicationContext, this func is for application controller
// get name of crossNamespace workload. The logic of this func is same with the way of appConfig generating workloadName
func (h *appHandler) getWorkloadName(w runtime.RawExtension, componentName string, revisionName string) (string, error) {
workload, err := oamutil.RawExtension2Unstructured(&w)
if err != nil {
return "", err
}
var revision int = 0
if len(revisionName) != 0 {
r, err := utils.ExtractRevision(revisionName)
if err != nil {
return "", err
}
revision = r
}
applicationconfiguration.SetAppWorkloadInstanceName(componentName, workload, revision, strconv.FormatBool(h.inplace))
return workload.GetName(), nil
}
// getTraitName generate trait name. By default the trait name will be generated by applicationContext, this func is for application controller
// get name of crossNamespace trait. The logic of this func is same with the way of appConfig generating traitName
func (h *appHandler) getTraitName(ctx context.Context, componentName string, ct *v1alpha2.ComponentTrait, t *runtime.RawExtension) (string, error) {
trait, err := oamutil.RawExtension2Unstructured(t)
if err != nil {
return "", err
}
traitDef, err := oamutil.FetchTraitDefinition(ctx, h.r, h.r.dm, trait)
if err != nil {
if !apierrors.IsNotFound(err) {
return "", errors.Wrapf(err, "cannot find trait definition %q %q %q", trait.GetAPIVersion(), trait.GetKind(), trait.GetName())
}
traitDef = oamutil.GetDummyTraitDefinition(trait)
}
traitType := traitDef.Name
if strings.Contains(traitType, ".") {
traitType = strings.Split(traitType, ".")[0]
}
traitName := oamutil.GenTraitName(componentName, ct, traitType)
return traitName, nil
}
// recodeTrackedResource append cross namespace resource to apphandler's acrossNamespaceResources field
func (h *appHandler) recodeTrackedResource(resourceName string, resource runtime.RawExtension) error {
u, err := oamutil.RawExtension2Unstructured(&resource)
if err != nil {
return err
}
tr := new(v1beta1.TypedReference)
tr.Name = resourceName
tr.Namespace = u.GetNamespace()
tr.APIVersion = u.GetAPIVersion()
tr.Kind = u.GetKind()
h.acrossNamespaceResources = append(h.acrossNamespaceResources, *tr)
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 {
rt := new(v1beta1.ResourceTracker)
err := h.r.Get(ctx, ctypes.NamespacedName{Name: h.generateResourceTrackerName()}, rt)
if err != nil {
if apierrors.IsNotFound(err) {
// guarantee app status right
h.app.Status.ResourceTracker = nil
return nil
}
return err
}
applied := map[v1beta1.TypedReference]bool{}
if len(h.acrossNamespaceResources) == 0 {
h.app.Status.ResourceTracker = nil
if err := h.r.Delete(ctx, rt); err != nil {
return err
}
return nil
}
for _, resource := range h.acrossNamespaceResources {
applied[resource] = true
}
for _, ref := range rt.Status.TrackedResources {
if !applied[ref] {
resource := new(unstructured.Unstructured)
resource.SetAPIVersion(ref.APIVersion)
resource.SetKind(ref.Kind)
resource.SetNamespace(ref.Namespace)
resource.SetName(ref.Name)
err := h.r.Delete(ctx, resource)
if err != nil {
if apierrors.IsNotFound(err) {
continue
}
return err
}
}
}
// update resourceTracker status, recode applied across-namespace resources
rt.Status.TrackedResources = h.acrossNamespaceResources
if err := h.r.Status().Update(ctx, rt); err != nil {
return err
}
h.app.Status.ResourceTracker = &runtimev1alpha1.TypedReference{
Name: rt.Name,
Kind: v1beta1.ResourceTrackerGroupKind,
APIVersion: v1beta1.ResourceTrackerKindAPIVersion,
UID: rt.UID}
return nil
}
// handleResourceTracker check the namespace of all workloads and traits
// if one resource is across-namespace create resourceTracker and set in appHandler field
func (h *appHandler) handleResourceTracker(ctx context.Context, components []*v1alpha2.Component, ac *v1alpha2.ApplicationConfiguration) error {
resourceTracker := new(v1beta1.ResourceTracker)
needTracker := false
for _, c := range components {
u, err := oamutil.RawExtension2Unstructured(&c.Spec.Workload)
if err != nil {
return err
}
if checkResourceDiffWithApp(u, h.app.Namespace) {
needTracker = true
break
}
}
outLoop:
for _, acComponent := range ac.Spec.Components {
for _, t := range acComponent.Traits {
u, err := oamutil.RawExtension2Unstructured(&t.Trait)
if err != nil {
return err
}
if checkResourceDiffWithApp(u, h.app.Namespace) {
needTracker = true
break outLoop
}
}
}
if needTracker {
// check weather related resourceTracker is existed, if not create it
err := h.r.Get(ctx, ctypes.NamespacedName{Name: h.generateResourceTrackerName()}, resourceTracker)
if err == nil {
h.resourceTracker = resourceTracker
return nil
}
if apierrors.IsNotFound(err) {
resourceTracker = &v1beta1.ResourceTracker{
ObjectMeta: metav1.ObjectMeta{
Name: h.generateResourceTrackerName(),
},
}
if err = h.r.Client.Create(ctx, resourceTracker); err != nil {
return err
}
h.resourceTracker = resourceTracker
return nil
}
return err
}
return nil
}
+487 -2
View File
@@ -127,7 +127,7 @@ var _ = Describe("Test application cross namespace resource", func() {
return fmt.Errorf("appication status error ")
}
return nil
}, time.Second*600, time.Microsecond*300).Should(BeNil())
}, time.Second*300, time.Microsecond*300).Should(BeNil())
By("check resource is generated correctly")
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil())
var workload appsv1.Deployment
@@ -136,6 +136,10 @@ var _ = Describe("Test application cross namespace resource", func() {
if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, appContext); err != nil {
return fmt.Errorf("cannot generate AppContext %v", err)
}
checkRt := new(v1beta1.ResourceTracker)
if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name), checkRt); err != nil {
return err
}
component := &v1alpha2.Component{}
if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: componentName}, component); err != nil {
return fmt.Errorf("cannot generate component %v", err)
@@ -158,6 +162,12 @@ var _ = Describe("Test application cross namespace resource", func() {
if len(workload.OwnerReferences) != 1 || workload.OwnerReferences[0].UID != resourceTracker.UID {
return fmt.Errorf("wrokload ownerreference error")
}
if len(checkRt.Status.TrackedResources) != 1 {
return fmt.Errorf("resourceTracker status recode trackedResource length missmatch")
}
if checkRt.Status.TrackedResources[0].Name != workload.Name {
return fmt.Errorf("resourceTracker status recode trackedResource name mismatch recorded %s, actually %s", checkRt.Status.TrackedResources[0].Name, workload.Name)
}
return nil
}, time.Second*50, time.Microsecond*300).Should(BeNil())
@@ -296,6 +306,12 @@ var _ = Describe("Test application cross namespace resource", func() {
if len(trait.OwnerReferences) != 1 || trait.OwnerReferences[0].UID != resourceTracker.UID {
return fmt.Errorf("trait owner reference missmatch")
}
if len(resourceTracker.Status.TrackedResources) != 1 {
return fmt.Errorf("resourceTracker status recode trackedResource length missmatch")
}
if resourceTracker.Status.TrackedResources[0].Name != trait.Name {
return fmt.Errorf("resourceTracker status recode trackedResource name mismatch recorded %s, actually %s", resourceTracker.Status.TrackedResources[0].Name, trait.Name)
}
return nil
}, time.Second*60, time.Microsecond*300).Should(BeNil())
})
@@ -370,6 +386,12 @@ var _ = Describe("Test application cross namespace resource", func() {
if len(trait.OwnerReferences) != 1 || trait.OwnerReferences[0].UID != resourceTracker.UID {
return fmt.Errorf("trait owner reference missmatch")
}
if len(resourceTracker.Status.TrackedResources) != 1 {
return fmt.Errorf("resourceTracker status recode trackedResource length missmatch")
}
if resourceTracker.Status.TrackedResources[0].Name != trait.Name {
return fmt.Errorf("resourceTracker status recode trackedResource name mismatch recorded %s, actually %s", resourceTracker.Status.TrackedResources[0].Name, trait.Name)
}
return nil
}, time.Second*60, time.Microsecond*300).Should(BeNil())
@@ -500,6 +522,12 @@ var _ = Describe("Test application cross namespace resource", func() {
if app.Status.ResourceTracker == nil || app.Status.ResourceTracker.UID != resourceTracker.UID {
return fmt.Errorf("app status resourceTracker error")
}
if len(resourceTracker.Status.TrackedResources) != 1 {
return fmt.Errorf("resourceTracker status recode trackedResource length missmatch")
}
if resourceTracker.Status.TrackedResources[0].Name != crossDeplpoy.Name {
return fmt.Errorf("resourceTracker status recode trackedResource name mismatch recorded %s, actually %s", resourceTracker.Status.TrackedResources[0].Name, crossDeplpoy.Name)
}
return nil
}, time.Second*60, time.Microsecond*300).Should(BeNil())
By("update application by delete cross namespace workload, resource tracker will be deleted, then check app status")
@@ -547,7 +575,7 @@ var _ = Describe("Test application cross namespace resource", func() {
return fmt.Errorf("error : cross namespace workload still exist")
}
if app.Status.ResourceTracker != nil {
return fmt.Errorf("errror app status resourceTracker")
return fmt.Errorf("error app status resourceTracker")
}
return nil
}, time.Second*60, time.Microsecond*300).Should(BeNil())
@@ -625,12 +653,21 @@ var _ = Describe("Test application cross namespace resource", func() {
return fmt.Errorf("error workload number %v", err)
}
workload = depolys.Items[0]
if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name), resourceTracker); err != nil {
return err
}
if len(workload.OwnerReferences) != 1 || workload.OwnerReferences[0].UID != resourceTracker.UID {
return fmt.Errorf("wrokload ownerreference error")
}
if workload.Spec.Template.Spec.Containers[0].Image != "busybox" {
return fmt.Errorf("container image not match")
}
if len(resourceTracker.Status.TrackedResources) != 1 {
return fmt.Errorf("resourceTracker status recode trackedResource length missmatch")
}
if resourceTracker.Status.TrackedResources[0].Name != workload.Name {
return fmt.Errorf("resourceTracker status recode trackedResource name mismatch recorded %s, actually %s", resourceTracker.Status.TrackedResources[0].Name, workload.Name)
}
return nil
}, time.Second*50, time.Microsecond*300).Should(BeNil())
@@ -679,6 +716,15 @@ var _ = Describe("Test application cross namespace resource", func() {
if workload.Spec.Template.Spec.Containers[0].Image != "nginx" {
return fmt.Errorf("container image not match")
}
if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name), resourceTracker); err != nil {
return err
}
if len(resourceTracker.Status.TrackedResources) != 1 {
return fmt.Errorf("resourceTracker status recode trackedResource length missmatch")
}
if resourceTracker.Status.TrackedResources[0].Name != workload.Name {
return fmt.Errorf("resourceTracker status recode trackedResource name mismatch recorded %s, actually %s", resourceTracker.Status.TrackedResources[0].Name, workload.Name)
}
return nil
}, time.Second*60, time.Microsecond*1000).Should(BeNil())
@@ -704,6 +750,445 @@ var _ = Describe("Test application cross namespace resource", func() {
return nil
}, time.Second*30, time.Microsecond*300).Should(BeNil())
})
It("Test cross-namespace resource gc logic, delete a cross-ns component", func() {
var (
appName = "test-app-6"
app = new(v1beta1.Application)
component1Name = "test-app-6-comp-1"
component2Name = "test-app-6-comp-2"
)
By("install related definition")
crossCdJson, err := yaml.YAMLToJSON([]byte(crossCompDefYaml))
Expect(err).Should(BeNil())
ctd := new(v1beta1.ComponentDefinition)
Expect(json.Unmarshal(crossCdJson, ctd)).Should(BeNil())
Expect(k8sClient.Create(ctx, ctd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
app = &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: appName,
Namespace: namespace,
},
Spec: v1beta1.ApplicationSpec{
Components: []v1beta1.ApplicationComponent{
v1beta1.ApplicationComponent{
Name: component1Name,
Type: "cross-worker",
Properties: runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
},
v1beta1.ApplicationComponent{
Name: component2Name,
Type: "cross-worker",
Properties: runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
},
},
},
}
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
time.Sleep(3 * time.Second) // give informer cache to sync
resourceTracker := new(v1beta1.ResourceTracker)
By("create application will generate two workload, and generate resourceTracker")
Eventually(func() error {
app = new(v1beta1.Application)
if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil {
return fmt.Errorf("error to get application %v", err)
}
if app.Status.Phase != common.ApplicationRunning {
return fmt.Errorf("application status not running")
}
err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name), resourceTracker)
if err != nil {
return fmt.Errorf("error to generate resourceTracker %v", err)
}
crossOpts := []client.ListOption{
client.InNamespace(crossNamespace),
client.MatchingLabels{
oam.LabelAppName: appName,
},
}
//same, cross := new(appsv1.DeploymentList), new(appsv1.DeploymentList)
workloads := new(appsv1.DeploymentList)
err = k8sClient.List(ctx, workloads, crossOpts...)
if err != nil || len(workloads.Items) != 2 {
return fmt.Errorf("failed get workloads")
}
deploy1 := workloads.Items[0]
if len(deploy1.OwnerReferences) != 1 || deploy1.OwnerReferences[0].Kind != v1beta1.ResourceTrackerKind {
return fmt.Errorf("deploy1 have error ownerReference")
}
deploy2 := workloads.Items[1]
if len(deploy2.OwnerReferences) != 1 || deploy2.OwnerReferences[0].UID != resourceTracker.UID {
return fmt.Errorf("deploy2 have error ownerReference")
}
if app.Status.ResourceTracker == nil || app.Status.ResourceTracker.UID != resourceTracker.UID {
return fmt.Errorf("app status resourceTracker error")
}
if len(resourceTracker.Status.TrackedResources) != 2 {
return fmt.Errorf("resourceTracker status recode trackedResource length missmatch")
}
if resourceTracker.Status.TrackedResources[0].Namespace != crossNamespace || resourceTracker.Status.TrackedResources[1].Namespace != crossNamespace {
return fmt.Errorf("resourceTracker recorde namespace mismatch")
}
if resourceTracker.Status.TrackedResources[0].Name != deploy1.Name && resourceTracker.Status.TrackedResources[1].Name != deploy1.Name {
return fmt.Errorf("resourceTracker status recode trackedResource name mismatch recorded %s, actually %s", resourceTracker.Status.TrackedResources[0].Name, deploy1.Name)
}
if resourceTracker.Status.TrackedResources[0].Name != deploy2.Name && resourceTracker.Status.TrackedResources[1].Name != deploy2.Name {
return fmt.Errorf("resourceTracker status recode trackedResource name mismatch recorded %s, actually %s", resourceTracker.Status.TrackedResources[0].Name, deploy2.Name)
}
return nil
}, time.Second*60, time.Microsecond*300).Should(BeNil())
By("update application by delete a cross namespace workload, resource tracker will still exist, then check app status")
Eventually(func() error {
app = new(v1beta1.Application)
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil())
app.Spec.Components = app.Spec.Components[:1] // delete a component
return k8sClient.Update(ctx, app)
}, time.Second*30, time.Microsecond*300).Should(BeNil())
Eventually(func() error {
app = new(v1beta1.Application)
if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil {
return fmt.Errorf("error to get application %v", err)
}
if app.Status.Phase != common.ApplicationRunning {
return fmt.Errorf("application status not running")
}
err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name), resourceTracker)
if err != nil {
return fmt.Errorf("failed to get resourceTracker %v", err)
}
crossOpts := []client.ListOption{
client.InNamespace(crossNamespace),
client.MatchingLabels{
oam.LabelAppName: appName,
},
}
workloads := new(appsv1.DeploymentList)
err = k8sClient.List(ctx, workloads, crossOpts...)
if err != nil || len(workloads.Items) != 1 {
return fmt.Errorf("failed get cross namespace workload")
}
deploy := workloads.Items[0]
if len(deploy.OwnerReferences) != 1 || deploy.OwnerReferences[0].Kind != v1beta1.ResourceTrackerKind {
return fmt.Errorf("same ns deploy have error ownerReference")
}
checkRt := new(v1beta1.ResourceTracker)
err = k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name), checkRt)
if err != nil {
return fmt.Errorf("error get resourceTracker")
}
if app.Status.ResourceTracker == nil {
return fmt.Errorf("app status resourceTracker error")
}
if app.Status.ResourceTracker.UID != checkRt.UID {
return fmt.Errorf("error app status resourceTracker UID")
}
if len(checkRt.Status.TrackedResources) != 1 {
return fmt.Errorf("error resourceTracker status trackedResource")
}
return nil
}, time.Second*80, time.Microsecond*300).Should(BeNil())
By("deleting application will remove resourceTracker and related resourceTracker will be removed")
app = new(v1beta1.Application)
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil())
Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
Eventually(func() error {
err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name), resourceTracker)
if err == nil {
return fmt.Errorf("resourceTracker still exist")
}
if !apierrors.IsNotFound(err) {
return err
}
return nil
}, time.Second*60, time.Microsecond*300).Should(BeNil())
})
It("Test cross-namespace resource gc logic, delete a cross-ns trait", func() {
var (
appName = "test-app-7"
app = new(v1beta1.Application)
componentName = "test-app-7-comp"
)
By("install related definition")
crossCdJson, err := yaml.YAMLToJSON([]byte(crossCompDefYaml))
Expect(err).Should(BeNil())
ctd := new(v1beta1.ComponentDefinition)
Expect(json.Unmarshal(crossCdJson, ctd)).Should(BeNil())
Expect(k8sClient.Create(ctx, ctd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
crossTdJson, err := yaml.YAMLToJSON([]byte(crossNsTdYaml))
Expect(err).Should(BeNil())
td := new(v1beta1.TraitDefinition)
Expect(json.Unmarshal(crossTdJson, td)).Should(BeNil())
Expect(k8sClient.Create(ctx, td)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
app = &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: appName,
Namespace: namespace,
},
Spec: v1beta1.ApplicationSpec{
Components: []v1beta1.ApplicationComponent{
v1beta1.ApplicationComponent{
Name: componentName,
Type: "cross-worker",
Properties: runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
Traits: []v1beta1.ApplicationTrait{
v1beta1.ApplicationTrait{
Type: "cross-scaler",
Properties: runtime.RawExtension{Raw: []byte(`{"replicas": 0}`)},
},
},
},
},
},
}
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
time.Sleep(3 * time.Second) // give informer cache to sync
resourceTracker := new(v1beta1.ResourceTracker)
By("create app and check resource and app status")
Eventually(func() error {
app = new(v1beta1.Application)
if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil {
return fmt.Errorf("error to get application %v", err)
}
if app.Status.Phase != common.ApplicationRunning {
return fmt.Errorf("application status not running")
}
err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name), resourceTracker)
if err != nil {
return fmt.Errorf("error to get resourceTracker %v", err)
}
mts := new(v1alpha2.ManualScalerTraitList)
opts := []client.ListOption{
client.InNamespace(crossNamespace),
client.MatchingLabels{
oam.LabelAppName: appName,
},
}
err = k8sClient.List(ctx, mts, opts...)
if err != nil || len(mts.Items) != 1 {
return fmt.Errorf("failed generate cross namespace trait")
}
if len(resourceTracker.Status.TrackedResources) != 2 {
return fmt.Errorf("resourceTracker status recode trackedResource length missmatch")
}
trait := mts.Items[0]
if len(trait.OwnerReferences) != 1 || trait.OwnerReferences[0].UID != resourceTracker.UID {
return fmt.Errorf("trait owner reference missmatch")
}
deploys := new(appsv1.DeploymentList)
err = k8sClient.List(ctx, deploys, opts...)
if err != nil || len(deploys.Items) != 1 {
return fmt.Errorf("error to list deploy")
}
deploy := deploys.Items[0]
if deploy.OwnerReferences[0].UID != resourceTracker.UID {
return fmt.Errorf("deploy owner reference missmatch")
}
for _, resource := range resourceTracker.Status.TrackedResources {
if resource.Kind == deploy.Kind && resource.Name != deploy.Name {
return fmt.Errorf("deploy name mismatch ")
}
if resource.Kind == trait.Kind && resource.Name != trait.Name {
return fmt.Errorf("trait name mismatch")
}
}
return nil
}, time.Second*60, time.Microsecond*300).Should(BeNil())
By("update application trait by delete cross ns trait, resourceTracker will still exist")
Eventually(func() error {
app = new(v1beta1.Application)
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil())
app.Spec.Components[0].Traits = []v1beta1.ApplicationTrait{}
return k8sClient.Update(ctx, app)
}, time.Second*30, time.Microsecond*300).Should(BeNil())
Eventually(func() error {
app = new(v1beta1.Application)
if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil {
return fmt.Errorf("error to get application %v", err)
}
if app.Status.Phase != common.ApplicationRunning {
return fmt.Errorf("application status not running")
}
err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name), resourceTracker)
if err != nil {
return fmt.Errorf("error to get resourceTracker %v", err)
}
mts := new(v1alpha2.ManualScalerTraitList)
opts := []client.ListOption{
client.InNamespace(crossNamespace),
client.MatchingLabels{
oam.LabelAppName: appName,
},
}
err = k8sClient.List(ctx, mts, opts...)
if err != nil || len(mts.Items) != 0 {
return fmt.Errorf("cross namespace trait still exist")
}
if len(resourceTracker.Status.TrackedResources) != 1 {
return fmt.Errorf("resourceTracker status recode trackedResource length missmatch")
}
deploys := new(appsv1.DeploymentList)
err = k8sClient.List(ctx, deploys, opts...)
if err != nil || len(deploys.Items) != 1 {
return fmt.Errorf("error to list deploy")
}
deploy := deploys.Items[0]
if len(deploy.OwnerReferences) != 1 || deploy.OwnerReferences[0].UID != resourceTracker.UID {
return fmt.Errorf("deploy owner reference missmatch")
}
if resourceTracker.Status.TrackedResources[0].Name != deploy.Name {
return fmt.Errorf("error to record deploy name in app status")
}
return nil
}, time.Second*60, time.Microsecond*300).Should(BeNil())
By("deleting application will remove resourceTracker and related resourceTracker will be removed")
app = new(v1beta1.Application)
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil())
Expect(k8sClient.Delete(ctx, app)).Should(BeNil())
Eventually(func() error {
err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name), resourceTracker)
if err == nil {
return fmt.Errorf("resourceTracker still exist")
}
if !apierrors.IsNotFound(err) {
return err
}
return nil
}, time.Second*60, time.Microsecond*300).Should(BeNil())
})
It("Test cross-namespace resource gc logic, update a cross-ns workload's namespace", func() {
// install related definition
crossCdJson, _ := yaml.YAMLToJSON([]byte(crossCompDefYaml))
ccd := new(v1beta1.ComponentDefinition)
Expect(json.Unmarshal(crossCdJson, ccd)).Should(BeNil())
Expect(k8sClient.Create(ctx, ccd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
normalCdJson, _ := yaml.YAMLToJSON([]byte(normalCompDefYaml))
ncd := new(v1beta1.ComponentDefinition)
Expect(json.Unmarshal(normalCdJson, ncd)).Should(BeNil())
Expect(k8sClient.Create(ctx, ncd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
var (
appName = "test-app-8"
app = new(v1beta1.Application)
componentName = "test-app-8-comp"
)
app = &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: appName,
Namespace: namespace,
},
Spec: v1beta1.ApplicationSpec{
Components: []v1beta1.ApplicationComponent{
v1beta1.ApplicationComponent{
Name: componentName,
Type: "cross-worker",
Properties: runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
},
},
},
}
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
By("check resource tracker has been created and app status ")
resourceTracker := new(v1beta1.ResourceTracker)
Eventually(func() error {
app := new(v1beta1.Application)
if err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app); err != nil {
return fmt.Errorf("app not found %v", err)
}
if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name), resourceTracker); err != nil {
return err
}
if app.Status.Phase != common.ApplicationRunning {
return fmt.Errorf("application status is not running")
}
if app.Status.ResourceTracker == nil || app.Status.ResourceTracker.UID != resourceTracker.UID {
return fmt.Errorf("appication status error ")
}
return nil
}, time.Second*60, time.Microsecond*300).Should(BeNil())
By("check resource is generated correctly")
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)).Should(BeNil())
var workload appsv1.Deployment
Eventually(func() error {
checkRt := new(v1beta1.ResourceTracker)
if err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name), checkRt); err != nil {
return err
}
depolys := new(appsv1.DeploymentList)
opts := []client.ListOption{
client.InNamespace(crossNamespace),
client.MatchingLabels{
oam.LabelAppName: appName,
},
}
err := k8sClient.List(ctx, depolys, opts...)
if err != nil || len(depolys.Items) != 1 {
return fmt.Errorf("error workload number %v", err)
}
workload = depolys.Items[0]
if len(workload.OwnerReferences) != 1 || workload.OwnerReferences[0].UID != resourceTracker.UID {
return fmt.Errorf("wrokload ownerreference error")
}
if len(checkRt.Status.TrackedResources) != 1 {
return fmt.Errorf("resourceTracker status recode trackedResource length missmatch")
}
if checkRt.Status.TrackedResources[0].Name != workload.Name {
return fmt.Errorf("resourceTracker status recode trackedResource name mismatch recorded %s, actually %s", checkRt.Status.TrackedResources[0].Name, workload.Name)
}
return nil
}, time.Second*60, time.Microsecond*300).Should(BeNil())
By("update application modify workload namespace will remove resourceTracker and related old workload will be removed")
time.Sleep(3 * time.Second) // wait informer cache to be synced
Eventually(func() error {
app = new(v1beta1.Application)
err := k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: appName}, app)
if err != nil {
return err
}
app.Spec.Components[0].Type = "normal-worker"
err = k8sClient.Update(ctx, app)
if err != nil {
return err
}
return nil
}, time.Second*30, time.Microsecond).Should(BeNil())
Eventually(func() error {
err := k8sClient.Get(ctx, generateResourceTrackerKey(app.Namespace, app.Name), resourceTracker)
if err == nil {
return fmt.Errorf("resourceTracker still exist")
}
if !apierrors.IsNotFound(err) {
return err
}
err = k8sClient.Get(ctx, types.NamespacedName{Namespace: crossNamespace, Name: workload.GetName()}, &workload)
if err == nil {
return fmt.Errorf("wrokload still exist")
}
if !apierrors.IsNotFound(err) {
return err
}
newWorkload := new(appsv1.Deployment)
err = k8sClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: workload.GetName()}, newWorkload)
if err != nil {
return fmt.Errorf("generate same namespace workload error")
}
return nil
}, time.Second*60, time.Microsecond*300).Should(BeNil())
})
})
func generateResourceTrackerKey(namespace string, name string) types.NamespacedName {