mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 04:26:39 +00:00
Feat: rework rt (#2797)
This commit is contained in:
@@ -479,6 +479,11 @@ type ClusterObjectReference struct {
|
||||
corev1.ObjectReference `json:",inline"`
|
||||
}
|
||||
|
||||
// Equal check if two references are equal
|
||||
func (in ClusterObjectReference) Equal(r ClusterObjectReference) bool {
|
||||
return in.APIVersion == r.APIVersion && in.Kind == r.Kind && in.Name == r.Name && in.Namespace == r.Namespace && in.UID == r.UID && in.Creator == r.Creator && in.Cluster == r.Cluster
|
||||
}
|
||||
|
||||
// RawExtensionPointer is the pointer of raw extension
|
||||
type RawExtensionPointer struct {
|
||||
RawExtension *runtime.RawExtension
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
@@ -257,32 +256,3 @@ type ScopeDefinitionList struct {
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []ScopeDefinition `json:"items"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// An ResourceTracker represents a tracker for track cross namespace resources
|
||||
// +kubebuilder:resource:scope=Cluster,categories={oam},shortName=tracker
|
||||
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 []corev1.ObjectReference `json:"trackedResources,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// ResourceTrackerList contains a list of ResourceTracker
|
||||
type ResourceTrackerList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []ResourceTracker `json:"items"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
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 v1beta1
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/interfaces"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// An ResourceTracker represents a tracker for track cross namespace resources
|
||||
// +kubebuilder:resource:scope=Cluster,categories={oam},shortName=tracker
|
||||
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 []common.ClusterObjectReference `json:"trackedResources,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// ResourceTrackerList contains a list of ResourceTracker
|
||||
type ResourceTrackerList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []ResourceTracker `json:"items"`
|
||||
}
|
||||
|
||||
// ToOwnerReference convert ResourceTracker into owner reference for other resource to refer
|
||||
func (in *ResourceTracker) ToOwnerReference() *metav1.OwnerReference {
|
||||
return &metav1.OwnerReference{
|
||||
APIVersion: SchemeGroupVersion.String(),
|
||||
Kind: ResourceTrackerKind,
|
||||
Name: in.Name,
|
||||
UID: in.UID,
|
||||
Controller: pointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: pointer.BoolPtr(true),
|
||||
}
|
||||
}
|
||||
|
||||
// AddOwnerReferenceToTrackerResource add resourcetracker as owner reference to target object, return true if already exists (outdated)
|
||||
func (in *ResourceTracker) AddOwnerReferenceToTrackerResource(rsc interfaces.ObjectOwner) bool {
|
||||
ownerRefs := []metav1.OwnerReference{*in.ToOwnerReference()}
|
||||
exists := false
|
||||
for _, owner := range rsc.GetOwnerReferences() {
|
||||
// delete the old resourceTracker owner
|
||||
if owner.Kind == ResourceTrackerKind && owner.APIVersion == SchemeGroupVersion.String() {
|
||||
exists = true
|
||||
continue
|
||||
}
|
||||
if owner.Controller != nil && *owner.Controller && owner.UID != in.UID {
|
||||
owner.Controller = pointer.BoolPtr(false)
|
||||
}
|
||||
ownerRefs = append(ownerRefs, owner)
|
||||
}
|
||||
rsc.SetOwnerReferences(ownerRefs)
|
||||
return exists
|
||||
}
|
||||
|
||||
func (in *ResourceTracker) addClusterObjectReference(ref common.ClusterObjectReference) bool {
|
||||
for _, _rsc := range in.Status.TrackedResources {
|
||||
if _rsc.Equal(ref) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
in.Status.TrackedResources = append(in.Status.TrackedResources, ref)
|
||||
return false
|
||||
}
|
||||
|
||||
// AddTrackedResource add new object reference into tracked resources, return if already exists
|
||||
func (in *ResourceTracker) AddTrackedResource(rsc interfaces.TrackableResource) bool {
|
||||
return in.addClusterObjectReference(common.ClusterObjectReference{
|
||||
ObjectReference: v1.ObjectReference{
|
||||
APIVersion: rsc.GetAPIVersion(),
|
||||
Kind: rsc.GetKind(),
|
||||
Name: rsc.GetName(),
|
||||
Namespace: rsc.GetNamespace(),
|
||||
UID: rsc.GetUID(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// AddTrackedCluster add resourcetracker in remote cluster into tracked resources, return if already exists
|
||||
func (in *ResourceTracker) AddTrackedCluster(clusterName string) bool {
|
||||
if clusterName == "" {
|
||||
return true
|
||||
}
|
||||
return in.addClusterObjectReference(common.ClusterObjectReference{
|
||||
Cluster: clusterName,
|
||||
ObjectReference: v1.ObjectReference{
|
||||
APIVersion: SchemeGroupVersion.String(),
|
||||
Kind: ResourceTrackerKind,
|
||||
Name: in.GetName(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetTrackedClusters return remote clusters recorded in the resource tracker
|
||||
func (in *ResourceTracker) GetTrackedClusters() (clusters []string) {
|
||||
for _, ref := range in.Status.TrackedResources {
|
||||
if ref.APIVersion == SchemeGroupVersion.String() && ref.Kind == ResourceTrackerKind && ref.Name == in.Name && ref.Cluster != "" {
|
||||
clusters = append(clusters, ref.Cluster)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// IsLifeLong check if resourcetracker shares the same whole life with the entire application
|
||||
func (in *ResourceTracker) IsLifeLong() bool {
|
||||
_, ok := in.GetAnnotations()[oam.AnnotationResourceTrackerLifeLong]
|
||||
return ok
|
||||
}
|
||||
|
||||
// SetLifeLong set life long to resource tracker
|
||||
func (in *ResourceTracker) SetLifeLong() {
|
||||
in.SetAnnotations(map[string]string{oam.AnnotationResourceTrackerLifeLong: "true"})
|
||||
}
|
||||
@@ -21,7 +21,6 @@ limitations under the License.
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
@@ -1040,7 +1039,7 @@ func (in *ResourceTrackerStatus) DeepCopyInto(out *ResourceTrackerStatus) {
|
||||
*out = *in
|
||||
if in.TrackedResources != nil {
|
||||
in, out := &in.TrackedResources, &out.TrackedResources
|
||||
*out = make([]corev1.ObjectReference, len(*in))
|
||||
*out = make([]common.ClusterObjectReference, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
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 interfaces
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
// ObjectOwner is the interface for get and set ownerReference
|
||||
type ObjectOwner interface {
|
||||
GetOwnerReferences() []metav1.OwnerReference
|
||||
SetOwnerReferences([]metav1.OwnerReference)
|
||||
}
|
||||
|
||||
// TrackableResource is the interface for resources to be tracked by resourcetracker
|
||||
type TrackableResource interface {
|
||||
client.Object
|
||||
metav1.Type
|
||||
ObjectOwner
|
||||
}
|
||||
@@ -42,35 +42,17 @@ spec:
|
||||
properties:
|
||||
trackedResources:
|
||||
items:
|
||||
description: 'ObjectReference contains enough information to let
|
||||
you inspect or modify the referred object. --- New uses of this
|
||||
type are discouraged because of difficulty describing its usage
|
||||
when embedded in APIs. 1. Ignored fields. It includes many fields
|
||||
which are not generally honored. For instance, ResourceVersion
|
||||
and FieldPath are both very rarely valid in actual usage. 2.
|
||||
Invalid usage help. It is impossible to add specific help for
|
||||
individual usage. In most embedded usages, there are particular restrictions
|
||||
like, "must refer only to types A and B" or "UID not honored"
|
||||
or "name must be restricted". Those cannot be well described
|
||||
when embedded. 3. Inconsistent validation. Because the usages
|
||||
are different, the validation rules are different by usage, which
|
||||
makes it hard for users to predict what will happen. 4. The fields
|
||||
are both imprecise and overly precise. Kind is not a precise
|
||||
mapping to a URL. This can produce ambiguity during interpretation
|
||||
and require a REST mapping. In most cases, the dependency is
|
||||
on the group,resource tuple and the version of the actual
|
||||
struct is irrelevant. 5. We cannot easily change it. Because
|
||||
this type is embedded in many locations, updates to this type will
|
||||
affect numerous schemas. Don''t make new APIs embed an underspecified
|
||||
API type they do not control. Instead of using this type, create
|
||||
a locally provided and used type that is well-focused on your
|
||||
reference. For example, ServiceReferences for admission registration:
|
||||
https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533
|
||||
.'
|
||||
description: ClusterObjectReference defines the object reference
|
||||
with cluster.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API version of the referent.
|
||||
type: string
|
||||
cluster:
|
||||
type: string
|
||||
creator:
|
||||
description: ResourceCreatorRole defines the resource creator.
|
||||
type: string
|
||||
fieldPath:
|
||||
description: 'If referring to a piece of an object instead of
|
||||
an entire object, this string should contain a valid JSON/Go
|
||||
|
||||
@@ -42,35 +42,17 @@ spec:
|
||||
properties:
|
||||
trackedResources:
|
||||
items:
|
||||
description: 'ObjectReference contains enough information to let
|
||||
you inspect or modify the referred object. --- New uses of this
|
||||
type are discouraged because of difficulty describing its usage
|
||||
when embedded in APIs. 1. Ignored fields. It includes many fields
|
||||
which are not generally honored. For instance, ResourceVersion
|
||||
and FieldPath are both very rarely valid in actual usage. 2.
|
||||
Invalid usage help. It is impossible to add specific help for
|
||||
individual usage. In most embedded usages, there are particular restrictions
|
||||
like, "must refer only to types A and B" or "UID not honored"
|
||||
or "name must be restricted". Those cannot be well described
|
||||
when embedded. 3. Inconsistent validation. Because the usages
|
||||
are different, the validation rules are different by usage, which
|
||||
makes it hard for users to predict what will happen. 4. The fields
|
||||
are both imprecise and overly precise. Kind is not a precise
|
||||
mapping to a URL. This can produce ambiguity during interpretation
|
||||
and require a REST mapping. In most cases, the dependency is
|
||||
on the group,resource tuple and the version of the actual
|
||||
struct is irrelevant. 5. We cannot easily change it. Because
|
||||
this type is embedded in many locations, updates to this type will
|
||||
affect numerous schemas. Don''t make new APIs embed an underspecified
|
||||
API type they do not control. Instead of using this type, create
|
||||
a locally provided and used type that is well-focused on your
|
||||
reference. For example, ServiceReferences for admission registration:
|
||||
https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533
|
||||
.'
|
||||
description: ClusterObjectReference defines the object reference
|
||||
with cluster.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API version of the referent.
|
||||
type: string
|
||||
cluster:
|
||||
type: string
|
||||
creator:
|
||||
description: ResourceCreatorRole defines the resource creator.
|
||||
type: string
|
||||
fieldPath:
|
||||
description: 'If referring to a piece of an object instead of
|
||||
an entire object, this string should contain a valid JSON/Go
|
||||
|
||||
@@ -42,35 +42,17 @@ spec:
|
||||
properties:
|
||||
trackedResources:
|
||||
items:
|
||||
description: 'ObjectReference contains enough information to let
|
||||
you inspect or modify the referred object. --- New uses of this
|
||||
type are discouraged because of difficulty describing its usage
|
||||
when embedded in APIs. 1. Ignored fields. It includes many fields
|
||||
which are not generally honored. For instance, ResourceVersion
|
||||
and FieldPath are both very rarely valid in actual usage. 2.
|
||||
Invalid usage help. It is impossible to add specific help for
|
||||
individual usage. In most embedded usages, there are particular restrictions
|
||||
like, "must refer only to types A and B" or "UID not honored"
|
||||
or "name must be restricted". Those cannot be well described
|
||||
when embedded. 3. Inconsistent validation. Because the usages
|
||||
are different, the validation rules are different by usage, which
|
||||
makes it hard for users to predict what will happen. 4. The fields
|
||||
are both imprecise and overly precise. Kind is not a precise
|
||||
mapping to a URL. This can produce ambiguity during interpretation
|
||||
and require a REST mapping. In most cases, the dependency is
|
||||
on the group,resource tuple and the version of the actual
|
||||
struct is irrelevant. 5. We cannot easily change it. Because
|
||||
this type is embedded in many locations, updates to this type will
|
||||
affect numerous schemas. Don''t make new APIs embed an underspecified
|
||||
API type they do not control. Instead of using this type, create
|
||||
a locally provided and used type that is well-focused on your
|
||||
reference. For example, ServiceReferences for admission registration:
|
||||
https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533
|
||||
.'
|
||||
description: ClusterObjectReference defines the object reference
|
||||
with cluster.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API version of the referent.
|
||||
type: string
|
||||
cluster:
|
||||
type: string
|
||||
creator:
|
||||
description: ResourceCreatorRole defines the resource creator.
|
||||
type: string
|
||||
fieldPath:
|
||||
description: 'If referring to a piece of an object instead of
|
||||
an entire object, this string should contain a valid JSON/Go
|
||||
|
||||
@@ -219,7 +219,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
|
||||
if wfStatus != nil {
|
||||
ref, err := handler.DispatchAndGC(ctx)
|
||||
if err == nil {
|
||||
err = multicluster.GarbageCollectionForOutdatedResourcesInSubClusters(ctx, app, func(c context.Context) error {
|
||||
err = multicluster.GarbageCollectionForOutdatedResourcesInSubClusters(ctx, r.Client, app, func(c context.Context) error {
|
||||
_, e := handler.DispatchAndGC(c)
|
||||
return e
|
||||
})
|
||||
@@ -332,23 +332,7 @@ func (r *Reconciler) handleFinalizers(ctx monitorContext.Context, app *v1beta1.A
|
||||
return true, errors.Wrap(r.Client.Update(ctx, app), errUpdateApplicationFinalizer)
|
||||
}
|
||||
if meta.FinalizerExists(app, resourceTrackerFinalizer) || meta.FinalizerExists(app, legacyOnlyRevisionFinalizer) {
|
||||
listOpts := []client.ListOption{
|
||||
client.MatchingLabels{
|
||||
oam.LabelAppName: app.Name,
|
||||
oam.LabelAppNamespace: app.Namespace,
|
||||
}}
|
||||
rtList := &v1beta1.ResourceTrackerList{}
|
||||
if err := r.Client.List(ctx, rtList, listOpts...); err != nil {
|
||||
ctx.Error(err, "Failed to list resource tracker of app", "name", app.Name)
|
||||
return true, errors.WithMessage(err, "cannot remove finalizer")
|
||||
}
|
||||
for _, rt := range rtList.Items {
|
||||
if err := r.Client.Delete(ctx, rt.DeepCopy()); err != nil && !kerrors.IsNotFound(err) {
|
||||
ctx.Error(err, "Failed to delete resource tracker", "name", rt.Name)
|
||||
return true, errors.WithMessage(err, "cannot remove finalizer")
|
||||
}
|
||||
}
|
||||
if err := multicluster.GarbageCollectionForAllResourceTrackersInSubCluster(ctx, r.Client, app); err != nil {
|
||||
if err := multicluster.GarbageCollectionForAllResourceTrackers(ctx, r.Client, app); err != nil {
|
||||
return true, err
|
||||
}
|
||||
meta.RemoveFinalizer(app, resourceTrackerFinalizer)
|
||||
|
||||
@@ -23,15 +23,12 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
kerrors "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/types"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
@@ -205,7 +202,7 @@ func (a *AppManifestsDispatcher) retrieveLegacyResourceTrackers(ctx context.Cont
|
||||
}
|
||||
for _, rt := range rtList.Items {
|
||||
if rt.Name != a.currentRTName &&
|
||||
(a.previousRT != nil && rt.Name != a.previousRT.Name) && !IsLifeLongResourceTracker(rt) {
|
||||
(a.previousRT != nil && rt.Name != a.previousRT.Name) && !rt.IsLifeLong() {
|
||||
a.legacyRTs = append(a.legacyRTs, rt.DeepCopy())
|
||||
}
|
||||
}
|
||||
@@ -222,7 +219,7 @@ func (a *AppManifestsDispatcher) retrieveLegacyResourceTrackers(ctx context.Cont
|
||||
if len(oldRtList.Items) != 0 {
|
||||
for _, rt := range oldRtList.Items {
|
||||
if rt.Name != a.currentRTName &&
|
||||
(a.previousRT != nil && rt.Name != a.previousRT.Name) && !IsLifeLongResourceTracker(rt) {
|
||||
(a.previousRT != nil && rt.Name != a.previousRT.Name) && !rt.IsLifeLong() {
|
||||
a.legacyRTs = append(a.legacyRTs, rt.DeepCopy())
|
||||
}
|
||||
}
|
||||
@@ -232,36 +229,13 @@ func (a *AppManifestsDispatcher) retrieveLegacyResourceTrackers(ctx context.Cont
|
||||
}
|
||||
|
||||
func (a *AppManifestsDispatcher) applyAndRecordManifests(ctx context.Context, manifests []*unstructured.Unstructured) error {
|
||||
ctrlUIDs := []types.UID{a.currentRT.UID}
|
||||
if a.previousRT != nil && a.previousRT.Name != a.currentRTName {
|
||||
klog.InfoS("Going to apply or upgrade resources", "from", a.previousRT.Name, "to", a.currentRTName)
|
||||
// if two RT's names are different, it means dispatching operation happens in an upgrade or rollout scenario
|
||||
// in such two scenarios, for those unchanged manifests, we will
|
||||
// - make sure existing resources are controlled by any of these two resource trackers
|
||||
// - set new resource tracker as their controller owner
|
||||
ctrlUIDs = append(ctrlUIDs, a.previousRT.UID)
|
||||
}
|
||||
|
||||
// allow to apply changes to resources owned by legacy RTs
|
||||
for _, rt := range a.legacyRTs {
|
||||
ctrlUIDs = append(ctrlUIDs, rt.UID)
|
||||
}
|
||||
|
||||
applyOpts := []apply.ApplyOption{apply.MustBeControllableByAny(ctrlUIDs), apply.NotUpdateRenderHashEqual()}
|
||||
ownerRef := metav1.OwnerReference{
|
||||
APIVersion: v1beta1.SchemeGroupVersion.String(),
|
||||
Kind: reflect.TypeOf(v1beta1.ResourceTracker{}).Name(),
|
||||
Name: a.currentRT.Name,
|
||||
UID: a.currentRT.UID,
|
||||
Controller: pointer.BoolPtr(true),
|
||||
BlockOwnerDeletion: pointer.BoolPtr(true),
|
||||
}
|
||||
applyOpts := []apply.ApplyOption{apply.MustBeControlledByApp(&a.appRev.Spec.Application), apply.NotUpdateRenderHashEqual()}
|
||||
for _, rsc := range manifests {
|
||||
if rsc == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
immutable, err := a.ImmutableResourcesUpdate(ctx, rsc, ownerRef, applyOpts)
|
||||
immutable, err := a.ImmutableResourcesUpdate(ctx, rsc, a.currentRT, applyOpts)
|
||||
if immutable {
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "Failed to apply immutable resource with new ownerReference", "object",
|
||||
@@ -273,7 +247,7 @@ func (a *AppManifestsDispatcher) applyAndRecordManifests(ctx context.Context, ma
|
||||
}
|
||||
|
||||
// each resource applied by dispatcher MUST be controlled by resource tracker
|
||||
setOrOverrideOAMControllerOwner(rsc, ownerRef)
|
||||
a.currentRT.AddOwnerReferenceToTrackerResource(rsc)
|
||||
if err := a.applicator.Apply(ctx, rsc, applyOpts...); err != nil {
|
||||
klog.ErrorS(err, "Failed to apply a resource", "object",
|
||||
klog.KObj(rsc), "apiVersion", rsc.GetAPIVersion(), "kind", rsc.GetKind())
|
||||
@@ -288,7 +262,7 @@ func (a *AppManifestsDispatcher) applyAndRecordManifests(ctx context.Context, ma
|
||||
|
||||
// ImmutableResourcesUpdate only updates the ownerReference
|
||||
// TODO(wonderflow): we should allow special fields to be updated. e.g. the resources.requests for bound claims for PV should be able to update
|
||||
func (a *AppManifestsDispatcher) ImmutableResourcesUpdate(ctx context.Context, res *unstructured.Unstructured, ownerRef metav1.OwnerReference, applyOpts []apply.ApplyOption) (bool, error) {
|
||||
func (a *AppManifestsDispatcher) ImmutableResourcesUpdate(ctx context.Context, res *unstructured.Unstructured, rt *v1beta1.ResourceTracker, applyOpts []apply.ApplyOption) (bool, error) {
|
||||
if res == nil {
|
||||
return false, nil
|
||||
}
|
||||
@@ -302,7 +276,7 @@ func (a *AppManifestsDispatcher) ImmutableResourcesUpdate(ctx context.Context, r
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
setOrOverrideOAMControllerOwner(pv, ownerRef)
|
||||
rt.AddOwnerReferenceToTrackerResource(pv)
|
||||
pv.SetGroupVersionKind(v1.SchemeGroupVersion.WithKind(reflect.TypeOf(v1.PersistentVolume{}).Name()))
|
||||
return true, a.applicator.Apply(ctx, pv, applyOpts...)
|
||||
default:
|
||||
@@ -313,29 +287,13 @@ func (a *AppManifestsDispatcher) ImmutableResourcesUpdate(ctx context.Context, r
|
||||
func (a *AppManifestsDispatcher) updateResourceTrackerStatus(ctx context.Context, appliedManifests []*unstructured.Unstructured) error {
|
||||
// merge applied resources and already tracked ones
|
||||
if a.currentRT.Status.TrackedResources == nil {
|
||||
a.currentRT.Status.TrackedResources = make([]v1.ObjectReference, 0)
|
||||
a.currentRT.Status.TrackedResources = make([]common.ClusterObjectReference, 0)
|
||||
}
|
||||
for _, rsc := range appliedManifests {
|
||||
if rsc == nil {
|
||||
continue
|
||||
}
|
||||
appliedRef := v1.ObjectReference{
|
||||
APIVersion: rsc.GetAPIVersion(),
|
||||
Kind: rsc.GetKind(),
|
||||
Name: rsc.GetName(),
|
||||
Namespace: rsc.GetNamespace(),
|
||||
}
|
||||
alreadyTracked := false
|
||||
for _, tracked := range a.currentRT.Status.TrackedResources {
|
||||
if tracked.APIVersion == appliedRef.APIVersion && tracked.Kind == appliedRef.Kind &&
|
||||
tracked.Name == appliedRef.Name && tracked.Namespace == appliedRef.Namespace {
|
||||
alreadyTracked = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !alreadyTracked {
|
||||
a.currentRT.Status.TrackedResources = append(a.currentRT.Status.TrackedResources, appliedRef)
|
||||
}
|
||||
a.currentRT.AddTrackedResource(rsc)
|
||||
}
|
||||
|
||||
// TODO move TrackedResources from status to spec
|
||||
@@ -355,36 +313,3 @@ func (a *AppManifestsDispatcher) updateResourceTrackerStatus(ctx context.Context
|
||||
klog.InfoS("Successfully update resource tracker status", "resourceTracker", a.currentRTName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ObjectOwner is a interface for get and set ownerReference
|
||||
type ObjectOwner interface {
|
||||
GetOwnerReferences() []metav1.OwnerReference
|
||||
SetOwnerReferences([]metav1.OwnerReference)
|
||||
}
|
||||
|
||||
// setOrOverrideOAMControllerOwner will set the new owner and remove the legacy OAM owner
|
||||
func setOrOverrideOAMControllerOwner(obj ObjectOwner, controllerOwner metav1.OwnerReference) {
|
||||
newOwnerRefs := []metav1.OwnerReference{controllerOwner}
|
||||
for _, owner := range obj.GetOwnerReferences() {
|
||||
// delete the old resourceTracker owner
|
||||
if owner.Kind == v1beta1.ResourceTrackerKind && owner.APIVersion == v1beta1.SchemeGroupVersion.String() {
|
||||
continue
|
||||
}
|
||||
// delete the old appContext owner
|
||||
if owner.Kind == "ApplicationContext" && owner.APIVersion == v1alpha2.SchemeGroupVersion.String() {
|
||||
continue
|
||||
}
|
||||
if owner.Controller != nil && *owner.Controller &&
|
||||
owner.UID != controllerOwner.UID {
|
||||
owner.Controller = pointer.BoolPtr(false)
|
||||
}
|
||||
newOwnerRefs = append(newOwnerRefs, owner)
|
||||
}
|
||||
obj.SetOwnerReferences(newOwnerRefs)
|
||||
}
|
||||
|
||||
// IsLifeLongResourceTracker check if resourcetracker shares the same whole life with the entire application
|
||||
func IsLifeLongResourceTracker(rt v1beta1.ResourceTracker) bool {
|
||||
_, ok := rt.GetAnnotations()[oam.AnnotationResourceTrackerLifeLong]
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -22,14 +22,17 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/interfaces"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
func TestSetOAMOwner(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
OO ObjectOwner
|
||||
OO interfaces.ObjectOwner
|
||||
CO v1.OwnerReference
|
||||
ExpOwner []v1.OwnerReference
|
||||
}{
|
||||
@@ -41,37 +44,11 @@ func TestSetOAMOwner(t *testing.T) {
|
||||
Name: "myapp",
|
||||
},
|
||||
ExpOwner: []v1.OwnerReference{{
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
Kind: "ResourceTracker",
|
||||
Name: "myapp",
|
||||
}},
|
||||
},
|
||||
"test remove old resourceTracker owner": {
|
||||
OO: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"ownerReferences": []interface{}{
|
||||
map[string]interface{}{
|
||||
"apiVersion": "core.oam.dev/v1beta1",
|
||||
"kind": "ResourceTracker",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"apiVersion": "core.oam.dev/v1alpha2",
|
||||
"kind": "ApplicationContext",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
CO: v1.OwnerReference{
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
Kind: "ResourceTracker",
|
||||
Name: "myapp",
|
||||
},
|
||||
ExpOwner: []v1.OwnerReference{{
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
Kind: "ResourceTracker",
|
||||
Name: "myapp",
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
Kind: "ResourceTracker",
|
||||
Name: "myapp",
|
||||
Controller: pointer.Bool(true),
|
||||
BlockOwnerDeletion: pointer.Bool(true),
|
||||
}},
|
||||
},
|
||||
"test other owner not removed": {
|
||||
@@ -94,19 +71,22 @@ func TestSetOAMOwner(t *testing.T) {
|
||||
Name: "myapp",
|
||||
},
|
||||
ExpOwner: []v1.OwnerReference{{
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
Kind: "ResourceTracker",
|
||||
Name: "myapp",
|
||||
},
|
||||
{
|
||||
APIVersion: "core.oam.dev/v1alpha1",
|
||||
Kind: "Rollout",
|
||||
Name: "xxx",
|
||||
}},
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
Kind: "ResourceTracker",
|
||||
Name: "myapp",
|
||||
Controller: pointer.Bool(true),
|
||||
BlockOwnerDeletion: pointer.Bool(true),
|
||||
}, {
|
||||
APIVersion: "core.oam.dev/v1alpha1",
|
||||
Kind: "Rollout",
|
||||
Name: "xxx",
|
||||
}},
|
||||
},
|
||||
}
|
||||
for name, ti := range tests {
|
||||
setOrOverrideOAMControllerOwner(ti.OO, ti.CO)
|
||||
rt := &v1beta1.ResourceTracker{}
|
||||
rt.Name = ti.CO.Name
|
||||
rt.AddOwnerReferenceToTrackerResource(ti.OO)
|
||||
assert.Equal(t, ti.ExpOwner, ti.OO.GetOwnerReferences(), name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
)
|
||||
|
||||
@@ -33,31 +34,39 @@ func TestIsTrackedResources(t *testing.T) {
|
||||
}{{
|
||||
oldRT: &v1beta1.ResourceTracker{
|
||||
Status: v1beta1.ResourceTrackerStatus{
|
||||
TrackedResources: []corev1.ObjectReference{{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
TrackedResources: []common.ClusterObjectReference{{
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
},
|
||||
}, {
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
newRT: &v1beta1.ResourceTracker{
|
||||
Status: v1beta1.ResourceTrackerStatus{
|
||||
TrackedResources: []corev1.ObjectReference{{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
TrackedResources: []common.ClusterObjectReference{{
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
},
|
||||
}, {
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
@@ -65,31 +74,39 @@ func TestIsTrackedResources(t *testing.T) {
|
||||
}, {
|
||||
oldRT: &v1beta1.ResourceTracker{
|
||||
Status: v1beta1.ResourceTrackerStatus{
|
||||
TrackedResources: []corev1.ObjectReference{{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
TrackedResources: []common.ClusterObjectReference{{
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
},
|
||||
}, {
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
Name: "hello",
|
||||
Namespace: "default",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
Name: "hello",
|
||||
Namespace: "default",
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
newRT: &v1beta1.ResourceTracker{
|
||||
Status: v1beta1.ResourceTrackerStatus{
|
||||
TrackedResources: []corev1.ObjectReference{{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
TrackedResources: []common.ClusterObjectReference{{
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
},
|
||||
}, {
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
@@ -98,16 +115,20 @@ func TestIsTrackedResources(t *testing.T) {
|
||||
oldRT: &v1beta1.ResourceTracker{},
|
||||
newRT: &v1beta1.ResourceTracker{
|
||||
Status: v1beta1.ResourceTrackerStatus{
|
||||
TrackedResources: []corev1.ObjectReference{{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
TrackedResources: []common.ClusterObjectReference{{
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
},
|
||||
}, {
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
@@ -115,16 +136,20 @@ func TestIsTrackedResources(t *testing.T) {
|
||||
}, {
|
||||
oldRT: &v1beta1.ResourceTracker{
|
||||
Status: v1beta1.ResourceTrackerStatus{
|
||||
TrackedResources: []corev1.ObjectReference{{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
TrackedResources: []common.ClusterObjectReference{{
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Kind: "Deployment",
|
||||
APIVersion: "apps/v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
},
|
||||
}, {
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/cue/model"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/resourcetracker"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
@@ -598,27 +599,6 @@ func ComputeComponentRevisionHash(comp *types.ComponentManifest) (string, error)
|
||||
return utils.ComputeSpecHash(&compRevisionHash)
|
||||
}
|
||||
|
||||
// createOrGetResourceTracker create or get a resource tracker to manage all componentRevisions
|
||||
func (h *AppHandler) createOrGetResourceTracker(ctx context.Context) (*v1beta1.ResourceTracker, error) {
|
||||
rt := &v1beta1.ResourceTracker{}
|
||||
rtName := h.app.Name + "-" + h.app.Namespace
|
||||
if err := h.r.Get(ctx, ktypes.NamespacedName{Name: rtName}, rt); err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
rt.SetName(rtName)
|
||||
rt.SetLabels(map[string]string{
|
||||
oam.LabelAppName: h.app.Name,
|
||||
oam.LabelAppNamespace: h.app.Namespace,
|
||||
})
|
||||
rt.SetAnnotations(map[string]string{oam.AnnotationResourceTrackerLifeLong: "true"})
|
||||
if err = h.r.Create(ctx, rt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return rt, nil
|
||||
}
|
||||
|
||||
// createControllerRevision records snapshot of a component
|
||||
func (h *AppHandler) createControllerRevision(ctx context.Context, cm *types.ComponentManifest) error {
|
||||
comp, err := componentManifest2Component(cm)
|
||||
@@ -626,7 +606,7 @@ func (h *AppHandler) createControllerRevision(ctx context.Context, cm *types.Com
|
||||
return err
|
||||
}
|
||||
revision, _ := utils.ExtractRevision(cm.RevisionName)
|
||||
rt, err := h.createOrGetResourceTracker(ctx)
|
||||
rt, err := resourcetracker.CreateOrGetApplicationRootResourceTracker(ctx, h.r.Client, h.app)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -798,7 +778,7 @@ func gatherUsingAppRevision(ctx context.Context, h *AppHandler) (map[string]bool
|
||||
return nil, err
|
||||
}
|
||||
for _, rt := range rtList.Items {
|
||||
if dispatch.IsLifeLongResourceTracker(rt) {
|
||||
if rt.IsLifeLong() {
|
||||
continue
|
||||
}
|
||||
appRev := dispatch.ExtractAppRevisionName(rt.Name, ns)
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
v1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
@@ -317,14 +316,7 @@ func (h *handler) recordWorkloadInResourceTracker(ctx context.Context, workload
|
||||
klog.Errorf("fail to get resourceTracker to record workload rollout: namespace:%s, name: %s", h.rollout.Namespace, h.rollout.Name)
|
||||
return err
|
||||
}
|
||||
recordedWorkload := corev1.ObjectReference{
|
||||
APIVersion: workload.GetAPIVersion(),
|
||||
Kind: workload.GetKind(),
|
||||
UID: workload.GetUID(),
|
||||
Namespace: workload.GetNamespace(),
|
||||
Name: workload.GetName(),
|
||||
}
|
||||
rt.Status.TrackedResources = append(rt.Status.TrackedResources, recordedWorkload)
|
||||
rt.AddTrackedResource(workload)
|
||||
if err := h.Status().Update(ctx, &rt); err != nil {
|
||||
klog.Errorf("fail to update resourceTracker for rollout record workload namespace:%s, name: %s", h.rollout.Namespace, h.rollout.Name)
|
||||
return err
|
||||
|
||||
+56
-27
@@ -27,24 +27,36 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/policy/envbinding"
|
||||
"github.com/oam-dev/kubevela/pkg/resourcetracker"
|
||||
errors2 "github.com/oam-dev/kubevela/pkg/utils/errors"
|
||||
)
|
||||
|
||||
func getAppliedClusters(app *v1beta1.Application) []string {
|
||||
func getClustersFromRootResourceTracker(ctx context.Context, c client.Client, app *v1beta1.Application) []string {
|
||||
rt, err := resourcetracker.GetApplicationRootResourceTracker(ctx, c, app)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return rt.GetTrackedClusters()
|
||||
}
|
||||
|
||||
func getAppliedClusters(ctx context.Context, c client.Client, app *v1beta1.Application) []string {
|
||||
appliedClusters := map[string]bool{}
|
||||
for _, v := range app.Status.AppliedResources {
|
||||
appliedClusters[v.Cluster] = true
|
||||
}
|
||||
status, err := envbinding.GetEnvBindingPolicyStatus(app, "")
|
||||
appliedClusters := map[string]bool{}
|
||||
if err != nil {
|
||||
klog.InfoS("failed to get envbinding policy status during gc", "err", err.Error())
|
||||
// fallback
|
||||
for _, v := range app.Status.AppliedResources {
|
||||
appliedClusters[v.Cluster] = true
|
||||
}
|
||||
klog.InfoS("failed to get envbinding policy status during gc", "err", err.Error())
|
||||
}
|
||||
if status != nil {
|
||||
for _, conn := range status.ClusterConnections {
|
||||
appliedClusters[conn.ClusterName] = true
|
||||
}
|
||||
}
|
||||
for _, cluster := range getClustersFromRootResourceTracker(ctx, c, app) {
|
||||
appliedClusters[cluster] = true
|
||||
}
|
||||
var clusters []string
|
||||
for cluster := range appliedClusters {
|
||||
clusters = append(clusters, cluster)
|
||||
@@ -53,9 +65,9 @@ func getAppliedClusters(app *v1beta1.Application) []string {
|
||||
}
|
||||
|
||||
// GarbageCollectionForOutdatedResourcesInSubClusters run garbage collection in sub clusters and remove outdated ResourceTrackers with their associated resources
|
||||
func GarbageCollectionForOutdatedResourcesInSubClusters(ctx context.Context, app *v1beta1.Application, gcHandler func(context.Context) error) error {
|
||||
func GarbageCollectionForOutdatedResourcesInSubClusters(ctx context.Context, c client.Client, app *v1beta1.Application, gcHandler func(context.Context) error) error {
|
||||
var errs errors2.ErrorList
|
||||
for _, clusterName := range getAppliedClusters(app) {
|
||||
for _, clusterName := range getAppliedClusters(ctx, c, app) {
|
||||
if err := gcHandler(ContextWithClusterName(ctx, clusterName)); err != nil {
|
||||
if !errors.As(err, &errors2.ResourceTrackerNotExistError{}) {
|
||||
errs.Append(errors.Wrapf(err, "failed to run gc in subCluster %s", clusterName))
|
||||
@@ -68,27 +80,44 @@ func GarbageCollectionForOutdatedResourcesInSubClusters(ctx context.Context, app
|
||||
return nil
|
||||
}
|
||||
|
||||
// GarbageCollectionForAllResourceTrackersInSubCluster run garbage collection in sub clusters and remove all ResourceTrackers for the EnvBinding
|
||||
func GarbageCollectionForAllResourceTrackersInSubCluster(ctx context.Context, c client.Client, app *v1beta1.Application) error {
|
||||
// delete subCluster resourceTracker
|
||||
for _, cluster := range getAppliedClusters(app) {
|
||||
subCtx := ContextWithClusterName(ctx, cluster)
|
||||
listOpts := []client.ListOption{
|
||||
client.MatchingLabels{
|
||||
oam.LabelAppName: app.Name,
|
||||
oam.LabelAppNamespace: app.Namespace,
|
||||
}}
|
||||
rtList := &v1beta1.ResourceTrackerList{}
|
||||
if err := c.List(subCtx, rtList, listOpts...); err != nil {
|
||||
klog.ErrorS(err, "failed to list resource tracker of app", "name", app.Name, "cluster", cluster)
|
||||
func garbageCollectResourceTrackers(ctx context.Context, c client.Client, app *v1beta1.Application, cluster string) error {
|
||||
if cluster != "" {
|
||||
ctx = ContextWithClusterName(ctx, cluster)
|
||||
}
|
||||
listOpts := []client.ListOption{
|
||||
client.MatchingLabels{
|
||||
oam.LabelAppName: app.Name,
|
||||
oam.LabelAppNamespace: app.Namespace,
|
||||
}}
|
||||
rtList := &v1beta1.ResourceTrackerList{}
|
||||
if err := c.List(ctx, rtList, listOpts...); err != nil {
|
||||
klog.ErrorS(err, "failed to list resource tracker of app", "name", app.Name, "cluster", cluster)
|
||||
return errors.WithMessage(err, "cannot remove finalizer")
|
||||
}
|
||||
for _, rt := range rtList.Items {
|
||||
if err := c.Delete(ctx, rt.DeepCopy()); err != nil && !kerrors.IsNotFound(err) {
|
||||
klog.ErrorS(err, "failed to delete resource tracker", "name", rt.Name)
|
||||
return errors.WithMessage(err, "cannot remove finalizer")
|
||||
}
|
||||
for _, rt := range rtList.Items {
|
||||
if err := c.Delete(subCtx, rt.DeepCopy()); err != nil && !kerrors.IsNotFound(err) {
|
||||
klog.ErrorS(err, "failed to delete resource tracker", "name", rt.Name)
|
||||
return errors.WithMessage(err, "cannot remove finalizer")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GarbageCollectionForAllResourceTrackersInSubCluster run garbage collection in sub clusters and remove all ResourceTrackers
|
||||
func GarbageCollectionForAllResourceTrackersInSubCluster(ctx context.Context, c client.Client, app *v1beta1.Application) error {
|
||||
// delete subCluster resourceTracker
|
||||
for _, cluster := range getAppliedClusters(ctx, c, app) {
|
||||
if err := garbageCollectResourceTrackers(ctx, c, app, cluster); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GarbageCollectionForAllResourceTrackers run garbage collection in sub clusters and remove all ResourceTrackers, including managed cluster
|
||||
func GarbageCollectionForAllResourceTrackers(ctx context.Context, c client.Client, app *v1beta1.Application) error {
|
||||
if err := GarbageCollectionForAllResourceTrackersInSubCluster(ctx, c, app); err != nil {
|
||||
return err
|
||||
}
|
||||
return garbageCollectResourceTrackers(ctx, c, app, "")
|
||||
}
|
||||
|
||||
@@ -17,16 +17,19 @@ limitations under the License.
|
||||
package multicluster
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
common2 "github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
func TestGetAppliedCluster(t *testing.T) {
|
||||
@@ -35,11 +38,12 @@ func TestGetAppliedCluster(t *testing.T) {
|
||||
app.Status.AppliedResources = []common.ClusterObjectReference{{
|
||||
Cluster: "cluster-0",
|
||||
}}
|
||||
cli := fake.NewClientBuilder().WithScheme(common2.Scheme).Build()
|
||||
app.Status.PolicyStatus = []common.PolicyStatus{{
|
||||
Type: v1alpha1.EnvBindingPolicyType,
|
||||
Status: &runtime.RawExtension{Raw: []byte(`bad value`)},
|
||||
}}
|
||||
clusters := getAppliedClusters(app)
|
||||
clusters := getAppliedClusters(context.Background(), cli, app)
|
||||
r.Equal(1, len(clusters))
|
||||
r.Equal("cluster-0", clusters[0])
|
||||
envBindingStatus := &v1alpha1.EnvBindingStatus{ClusterConnections: []v1alpha1.ClusterConnection{{
|
||||
@@ -49,11 +53,12 @@ func TestGetAppliedCluster(t *testing.T) {
|
||||
}}}
|
||||
bs, err := json.Marshal(envBindingStatus)
|
||||
r.NoError(err)
|
||||
app.Status.AppliedResources = []common.ClusterObjectReference{}
|
||||
app.Status.PolicyStatus = []common.PolicyStatus{{
|
||||
Type: v1alpha1.EnvBindingPolicyType,
|
||||
Status: &runtime.RawExtension{Raw: bs},
|
||||
}}
|
||||
clusters = getAppliedClusters(app)
|
||||
clusters = getAppliedClusters(context.Background(), cli, app)
|
||||
r.Equal(2, len(clusters))
|
||||
sort.Strings(clusters)
|
||||
r.Equal("cluster-1", clusters[0])
|
||||
|
||||
@@ -17,13 +17,17 @@ limitations under the License.
|
||||
package envbinding
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/resourcetracker"
|
||||
)
|
||||
|
||||
// ReadPlacementDecisions read placement decisions from application status, return (decisions, if decision is made, error)
|
||||
@@ -123,3 +127,23 @@ func WritePlacementDecisions(app *v1beta1.Application, policyName string, envNam
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteClustersToResourceTracker records cluster into application root resource tracker
|
||||
func WriteClustersToResourceTracker(ctx context.Context, c client.Client, app *v1beta1.Application, clusters ...string) error {
|
||||
rt, err := resourcetracker.CreateOrGetApplicationRootResourceTracker(ctx, c, app)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to create/get application root resource tracker")
|
||||
}
|
||||
update := false
|
||||
for _, cluster := range clusters {
|
||||
if exists := rt.AddTrackedCluster(cluster); !exists {
|
||||
update = true
|
||||
}
|
||||
}
|
||||
if update {
|
||||
if err = c.Status().Update(ctx, rt); err != nil {
|
||||
return errors.Wrapf(err, "failed to add cluster into application root resource tracker")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
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 resourcetracker
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
// GetApplicationRootResourceTracker get root resourcetracker for application
|
||||
// root resourcetracker if a life-long resourcetracker which shares the life-cycle with the application instead of one revision
|
||||
func GetApplicationRootResourceTracker(ctx context.Context, c client.Client, app *v1beta1.Application) (*v1beta1.ResourceTracker, error) {
|
||||
rt := &v1beta1.ResourceTracker{}
|
||||
rtName := app.Name + "-" + app.Namespace
|
||||
if err := c.Get(ctx, types.NamespacedName{Name: rtName}, rt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rt, nil
|
||||
}
|
||||
|
||||
// CreateOrGetApplicationRootResourceTracker create or get root resourcetracker for application
|
||||
// root resourcetracker if a life-long resourcetracker which shares the life-cycle with the application instead of one revision
|
||||
func CreateOrGetApplicationRootResourceTracker(ctx context.Context, c client.Client, app *v1beta1.Application) (*v1beta1.ResourceTracker, error) {
|
||||
rt, err := GetApplicationRootResourceTracker(ctx, c, app)
|
||||
if err != nil {
|
||||
if !errors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
rt = &v1beta1.ResourceTracker{}
|
||||
rtName := app.Name + "-" + app.Namespace
|
||||
rt.SetName(rtName)
|
||||
rt.SetLabels(map[string]string{
|
||||
oam.LabelAppName: app.Name,
|
||||
oam.LabelAppNamespace: app.Namespace,
|
||||
})
|
||||
rt.SetLifeLong()
|
||||
if err = c.Create(ctx, rt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return rt, nil
|
||||
}
|
||||
@@ -18,7 +18,10 @@ package apply
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
|
||||
@@ -273,6 +276,37 @@ func MustBeControllableByAny(ctrlUIDs []types.UID) ApplyOption {
|
||||
}
|
||||
}
|
||||
|
||||
// MustBeControlledByApp requires that the new object is controllable by versioned resourcetracker
|
||||
func MustBeControlledByApp(app *v1beta1.Application) ApplyOption {
|
||||
pattern := "^" + app.GetName() + "-v[0-9]+-" + app.GetNamespace() + "$"
|
||||
return func(_ *applyAction, existing, _ client.Object) error {
|
||||
if existing == nil {
|
||||
return nil
|
||||
}
|
||||
existingObjMeta, _ := existing.(metav1.Object)
|
||||
c := metav1.GetControllerOf(existingObjMeta)
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NOTE This is for backward compatibility after ApplicationContext is deprecated.
|
||||
// In legacy clusters, existing resources are ctrl-owned by ApplicationContext or ResourceTracker (only for
|
||||
// cx-namespace and cluster-scope resources). We use a particular annotation to identify legacy resources.
|
||||
if len(existingObjMeta.GetAnnotations()[oam.AnnotationKubeVelaVersion]) == 0 {
|
||||
// just skip checking UIDs, '3-way-merge' will remove the legacy ctrl-owner automatically
|
||||
return nil
|
||||
}
|
||||
|
||||
if !(c.APIVersion == v1beta1.SchemeGroupVersion.String()) || !(c.Kind == v1beta1.ResourceTrackerKind) {
|
||||
return fmt.Errorf("existing object is not controlled by resourcetracker, currently controlled by %s[%s]", c.Kind, c.APIVersion)
|
||||
}
|
||||
if !regexp.MustCompile(pattern).MatchString(c.Name) {
|
||||
return fmt.Errorf("existing object is controlled by resourcetracker %s which does not conform to pattern %s", c.Name, pattern)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MakeCustomApplyOption let user can generate applyOption that restrict change apply action.
|
||||
func MakeCustomApplyOption(f func(existing, desired client.Object) error) ApplyOption {
|
||||
return func(act *applyAction, existing, desired client.Object) error {
|
||||
|
||||
@@ -115,6 +115,9 @@ func (p *provider) MakePlacementDecisions(ctx wfContext.Context, v *value.Value,
|
||||
if err = envbinding.WritePlacementDecisions(p.app, policy, env, decisions); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = envbinding.WriteClustersToResourceTracker(context.Background(), p.Client, p.app, clusterName); err != nil {
|
||||
return err
|
||||
}
|
||||
return v.FillObject(map[string]interface{}{"decisions": decisions}, "outputs")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user