Skip to content

Commit 73e4c79

Browse files
authored
Fix: rebuild appliedResources from ResourceTracker instead of filtering by name (kubevela#7083)
appliedResources entries use resource names, not component names, so filtering them against component names incorrectly dropped valid entries. Rebuild directly from the current ResourceTracker each reconcile - already in memory, no extra API calls. Signed-off-by: Brian Kane <briankane1@gmail.com>
1 parent 69d046f commit 73e4c79

4 files changed

Lines changed: 109 additions & 99 deletions

File tree

pkg/controller/core.oam.dev/v1beta1/application/application_controller.go

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -238,20 +238,18 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
238238
}
239239

240240
handler.addServiceStatus(false, app.Status.Services...)
241+
app.Status.Services = handler.services
242+
241243
handler.addAppliedResource(true, app.Status.AppliedResources...)
242244
app.Status.AppliedResources = handler.appliedResources
243-
app.Status.Services = handler.services
244245

245-
// Remove status entries for components that no longer exist in spec
246-
filteredServices, filteredResources, componentsRemoved := filterRemovedComponentsFromStatus(
246+
// Remove services[] entries for components that no longer exist in spec
247+
filteredServices, componentsRemoved := filterRemovedComponentsFromStatus(
247248
app.Spec.Components,
248249
app.Status.Services,
249-
app.Status.AppliedResources,
250250
)
251251
app.Status.Services = filteredServices
252-
app.Status.AppliedResources = filteredResources
253252
handler.services = filteredServices
254-
handler.appliedResources = filteredResources
255253

256254
if componentsRemoved {
257255
logCtx.Info("Removed deleted components from status")
@@ -316,6 +314,10 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
316314
default:
317315
}
318316

317+
// Rebuild appliedResources from the ResourceTracker now that the workflow has finished
318+
// dispatching. The RT is the authoritative source
319+
app.Status.AppliedResources = handler.resourceKeeper.GetAppliedResources()
320+
319321
var phase = common.ApplicationRunning
320322
isHealthy := evalStatus(logCtx, handler, appFile, appParser)
321323
if !isHealthy {
@@ -853,13 +855,12 @@ func setVelaVersion(app *v1beta1.Application) {
853855
}
854856
}
855857

856-
// filterRemovedComponentsFromStatus removes status entries for components no longer in spec.
857-
// Returns filtered lists and whether any components were removed (used to determine Update vs Patch).
858+
// filterRemovedComponentsFromStatus removes services[] entries for components no longer in spec.
859+
// Returns filtered services and whether any were removed (used to determine Update vs Patch).
858860
func filterRemovedComponentsFromStatus(
859861
components []common.ApplicationComponent,
860862
services []common.ApplicationComponentStatus,
861-
appliedResources []common.ClusterObjectReference,
862-
) (filteredServices []common.ApplicationComponentStatus, filteredResources []common.ClusterObjectReference, removed bool) {
863+
) (filteredServices []common.ApplicationComponentStatus, removed bool) {
863864
componentMap := make(map[string]struct{}, len(components))
864865
for _, comp := range components {
865866
componentMap[comp.Name] = struct{}{}
@@ -874,16 +875,7 @@ func filterRemovedComponentsFromStatus(
874875
}
875876
}
876877

877-
filteredResources = make([]common.ClusterObjectReference, 0, len(appliedResources))
878-
for _, res := range appliedResources {
879-
if _, found := componentMap[res.Name]; found {
880-
filteredResources = append(filteredResources, res)
881-
} else {
882-
removed = true
883-
}
884-
}
885-
886-
return filteredServices, filteredResources, removed
878+
return filteredServices, removed
887879
}
888880

889881
func evalStatus(ctx monitorContext.Context, handler *AppHandler, appFile *appfile.Appfile, appParser *appfile.Parser) bool {

pkg/controller/core.oam.dev/v1beta1/application/evalstatus_test.go

Lines changed: 17 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323

2424
"cuelang.org/go/cue"
2525
"github.com/stretchr/testify/assert"
26-
corev1 "k8s.io/api/core/v1"
2726
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2827
"k8s.io/apimachinery/pkg/runtime"
2928

@@ -185,72 +184,36 @@ func Test_applyComponentHealthToServices(t *testing.T) {
185184

186185
func TestFilterRemovedComponentsFromStatus(t *testing.T) {
187186
tests := []struct {
188-
name string
189-
components []common.ApplicationComponent
190-
statusServices []common.ApplicationComponentStatus
191-
statusResources []common.ClusterObjectReference
192-
expectedServices []string
193-
expectedResources []string
194-
componentsRemoved bool
187+
name string
188+
components []common.ApplicationComponent
189+
statusServices []common.ApplicationComponentStatus
190+
expectedServices []string
191+
servicesRemoved bool
195192
}{
196193
{
197-
name: "removed components are filtered from status",
194+
name: "removed component is filtered from services",
198195
components: []common.ApplicationComponent{
199196
{Name: "backend", Type: "webservice"},
200197
},
201198
statusServices: []common.ApplicationComponentStatus{
202199
{Name: "frontend", Namespace: "default"},
203200
{Name: "backend", Namespace: "default"},
204201
},
205-
statusResources: []common.ClusterObjectReference{
206-
{
207-
ObjectReference: corev1.ObjectReference{
208-
Name: "frontend",
209-
Namespace: "default",
210-
Kind: "Deployment",
211-
},
212-
},
213-
{
214-
ObjectReference: corev1.ObjectReference{
215-
Name: "backend",
216-
Namespace: "default",
217-
Kind: "Deployment",
218-
},
219-
},
220-
},
221-
expectedServices: []string{"backend"},
222-
expectedResources: []string{"backend"},
223-
componentsRemoved: true,
202+
expectedServices: []string{"backend"},
203+
servicesRemoved: true,
224204
},
225205
{
226-
name: "all components removed results in empty status",
206+
name: "all components removed results in empty services",
227207
components: []common.ApplicationComponent{},
228208
statusServices: []common.ApplicationComponentStatus{
229209
{Name: "frontend", Namespace: "default"},
230210
{Name: "backend", Namespace: "default"},
231211
},
232-
statusResources: []common.ClusterObjectReference{
233-
{
234-
ObjectReference: corev1.ObjectReference{
235-
Name: "frontend",
236-
Namespace: "default",
237-
Kind: "Deployment",
238-
},
239-
},
240-
{
241-
ObjectReference: corev1.ObjectReference{
242-
Name: "backend",
243-
Namespace: "default",
244-
Kind: "Deployment",
245-
},
246-
},
247-
},
248-
expectedServices: []string{},
249-
expectedResources: []string{},
250-
componentsRemoved: true,
212+
expectedServices: []string{},
213+
servicesRemoved: true,
251214
},
252215
{
253-
name: "no components removed keeps all status entries",
216+
name: "no components removed keeps all services",
254217
components: []common.ApplicationComponent{
255218
{Name: "frontend", Type: "webservice"},
256219
{Name: "backend", Type: "webservice"},
@@ -259,52 +222,27 @@ func TestFilterRemovedComponentsFromStatus(t *testing.T) {
259222
{Name: "frontend", Namespace: "default"},
260223
{Name: "backend", Namespace: "default"},
261224
},
262-
statusResources: []common.ClusterObjectReference{
263-
{
264-
ObjectReference: corev1.ObjectReference{
265-
Name: "frontend",
266-
Namespace: "default",
267-
Kind: "Deployment",
268-
},
269-
},
270-
{
271-
ObjectReference: corev1.ObjectReference{
272-
Name: "backend",
273-
Namespace: "default",
274-
Kind: "Deployment",
275-
},
276-
},
277-
},
278-
expectedServices: []string{"frontend", "backend"},
279-
expectedResources: []string{"frontend", "backend"},
280-
componentsRemoved: false,
225+
expectedServices: []string{"frontend", "backend"},
226+
servicesRemoved: false,
281227
},
282228
}
283229

284230
for _, tt := range tests {
285231
t.Run(tt.name, func(t *testing.T) {
286-
filteredServices, filteredResources, componentsRemoved := filterRemovedComponentsFromStatus(
232+
filteredServices, servicesRemoved := filterRemovedComponentsFromStatus(
287233
tt.components,
288234
tt.statusServices,
289-
tt.statusResources,
290235
)
291236

292-
assert.Equal(t, tt.componentsRemoved, componentsRemoved,
293-
"componentsRemoved flag should match expected value")
237+
assert.Equal(t, tt.servicesRemoved, servicesRemoved,
238+
"servicesRemoved flag should match expected value")
294239

295240
assert.Equal(t, len(tt.expectedServices), len(filteredServices),
296241
"filtered services count should match expected")
297242
for i, expectedName := range tt.expectedServices {
298243
assert.Equal(t, expectedName, filteredServices[i].Name,
299244
fmt.Sprintf("service at index %d should be %s", i, expectedName))
300245
}
301-
302-
assert.Equal(t, len(tt.expectedResources), len(filteredResources),
303-
"filtered resources count should match expected")
304-
for i, expectedName := range tt.expectedResources {
305-
assert.Equal(t, expectedName, filteredResources[i].Name,
306-
fmt.Sprintf("resource at index %d should be %s", i, expectedName))
307-
}
308246
})
309247
}
310248
}

pkg/resourcekeeper/resourcekeeper.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2727
"sigs.k8s.io/controller-runtime/pkg/client"
2828

29+
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
2930
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
3031
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
3132
"github.com/oam-dev/kubevela/pkg/multicluster"
@@ -45,6 +46,9 @@ type ResourceKeeper interface {
4546

4647
DispatchComponentRevision(context.Context, *appsv1.ControllerRevision) error
4748
DeleteComponentRevision(context.Context, *appsv1.ControllerRevision) error
49+
50+
// GetAppliedResources returns the current applied resources from the ResourceTracker.
51+
GetAppliedResources() []common.ClusterObjectReference
4852
}
4953

5054
type resourceKeeper struct {
@@ -125,6 +129,20 @@ func (h *resourceKeeper) loadResourceTrackers(ctx context.Context) (err error) {
125129
return err
126130
}
127131

132+
// GetAppliedResources returns all resources from the current ResourceTracker as ClusterObjectReferences.
133+
// Resources pending deletion (Deleted=true) are included as they still exist in the cluster.
134+
// Returns an empty slice if no current ResourceTracker is loaded.
135+
func (h *resourceKeeper) GetAppliedResources() []common.ClusterObjectReference {
136+
if h._currentRT == nil {
137+
return []common.ClusterObjectReference{}
138+
}
139+
refs := make([]common.ClusterObjectReference, 0, len(h._currentRT.Spec.ManagedResources))
140+
for _, mr := range h._currentRT.Spec.ManagedResources {
141+
refs = append(refs, mr.ClusterObjectReference)
142+
}
143+
return refs
144+
}
145+
128146
// NewResourceKeeper create a handler for dispatching and deleting resources
129147
func NewResourceKeeper(ctx context.Context, cli client.Client, app *v1beta1.Application) (_ ResourceKeeper, err error) {
130148
h := &resourceKeeper{

pkg/resourcekeeper/resourcekeeper_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@ import (
2121
"fmt"
2222
"testing"
2323

24+
"github.com/stretchr/testify/assert"
2425
"github.com/stretchr/testify/require"
26+
corev1 "k8s.io/api/core/v1"
2527
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2628
"k8s.io/apimachinery/pkg/runtime"
2729
"sigs.k8s.io/controller-runtime/pkg/client/fake"
2830

31+
oamcommon "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
2932
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
3033
"github.com/oam-dev/kubevela/pkg/oam"
3134
"github.com/oam-dev/kubevela/pkg/oam/util"
@@ -97,3 +100,62 @@ func TestNewResourceKeeper(t *testing.T) {
97100
r.NotNil(currentRT)
98101
r.Equal(3, len(rk._historyRTs))
99102
}
103+
104+
func TestGetAppliedResources(t *testing.T) {
105+
ref := func(name, kind, component string, deleted bool) v1beta1.ManagedResource {
106+
return v1beta1.ManagedResource{
107+
ClusterObjectReference: oamcommon.ClusterObjectReference{
108+
Creator: oamcommon.WorkflowResourceCreator,
109+
ObjectReference: corev1.ObjectReference{
110+
Name: name,
111+
Namespace: "default",
112+
Kind: kind,
113+
APIVersion: "v1",
114+
},
115+
},
116+
OAMObjectReference: oamcommon.OAMObjectReference{Component: component},
117+
Deleted: deleted,
118+
}
119+
}
120+
121+
tests := []struct {
122+
name string
123+
managedRes []v1beta1.ManagedResource
124+
expectedNames []string
125+
}{
126+
{
127+
name: "returns all resources including pending-delete",
128+
managedRes: []v1beta1.ManagedResource{
129+
ref("shared-config", "ConfigMap", "my-component", false),
130+
ref("old-config", "ConfigMap", "removed-component", true),
131+
},
132+
expectedNames: []string{"shared-config", "old-config"},
133+
},
134+
{
135+
name: "returns empty when no current RT",
136+
managedRes: nil,
137+
expectedNames: []string{},
138+
},
139+
{
140+
name: "returns empty when RT has no resources",
141+
managedRes: []v1beta1.ManagedResource{},
142+
expectedNames: []string{},
143+
},
144+
}
145+
146+
for _, tt := range tests {
147+
t.Run(tt.name, func(t *testing.T) {
148+
rk := &resourceKeeper{}
149+
if tt.managedRes != nil {
150+
rk._currentRT = &v1beta1.ResourceTracker{
151+
Spec: v1beta1.ResourceTrackerSpec{ManagedResources: tt.managedRes},
152+
}
153+
}
154+
result := rk.GetAppliedResources()
155+
require.Equal(t, len(tt.expectedNames), len(result))
156+
for i, name := range tt.expectedNames {
157+
assert.Equal(t, name, result[i].Name)
158+
}
159+
})
160+
}
161+
}

0 commit comments

Comments
 (0)