Skip to content

Commit 0036857

Browse files
authored
Merge pull request #1128 from tkan145/THREESCALE-11949
THREESCALE 11949 - New application is created without deleting old application when AccountCR reference changes
2 parents 996654b + b9299f7 commit 0036857

6 files changed

Lines changed: 155 additions & 11 deletions

File tree

apis/capabilities/v1beta1/application_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type ApplicationSpec struct {
4141
// Important: Run "make" to regenerate code after modifying this file
4242

4343
// AccountCRName name of account custom resource under which the application will be created
44+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="AccountCR reference is immutable once set"
4445
AccountCR *corev1.LocalObjectReference `json:"accountCR"`
4546

4647
// ProductCRName of product custom resource from which the application plan will be used

config/crd/bases/capabilities.3scale.net_applications.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ spec:
5151
type: string
5252
type: object
5353
x-kubernetes-map-type: atomic
54+
x-kubernetes-validations:
55+
- message: AccountCR reference is immutable once set
56+
rule: self == oldSelf
5457
applicationPlanName:
5558
description: ApplicationPlanName name of application plan that the
5659
application will use

controllers/capabilities/application_controller_test.go

Lines changed: 115 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func TestApplicationReconciler_Reconcile(t *testing.T) {
8282
name string
8383
application *capabilitiesv1beta1.Application
8484
account *capabilitiesv1beta1.DeveloperAccount
85-
product *capabilitiesv1beta1.Product
85+
product []*capabilitiesv1beta1.Product
8686
httpHandlerOptions []mocks.ApplicationAPIHandlerOpt
8787
testBody func(t *testing.T, reconciler *reconcilers.BaseReconciler, req reconcile.Request)
8888
}{
@@ -105,7 +105,7 @@ func TestApplicationReconciler_Reconcile(t *testing.T) {
105105
Description: "test",
106106
},
107107
},
108-
product: getProductCR(),
108+
product: []*capabilitiesv1beta1.Product{getProductCR()},
109109
testBody: func(t *testing.T, r *reconcilers.BaseReconciler, req reconcile.Request) {
110110
applicationReconciler := ApplicationReconciler{BaseReconciler: r}
111111
_, err := applicationReconciler.Reconcile(context.Background(), req)
@@ -208,7 +208,7 @@ func TestApplicationReconciler_Reconcile(t *testing.T) {
208208
},
209209
},
210210
},
211-
product: getProductCR(),
211+
product: []*capabilitiesv1beta1.Product{getProductCR()},
212212
httpHandlerOptions: []mocks.ApplicationAPIHandlerOpt{
213213
mocks.WithService(3, getApplicationPlanListByProductJson()),
214214
mocks.WithAccount(3, &client.ApplicationList{Applications: []client.ApplicationElem{
@@ -250,7 +250,7 @@ func TestApplicationReconciler_Reconcile(t *testing.T) {
250250
},
251251
},
252252
account: getApplicationDeveloperAccount(),
253-
product: getProductCR(),
253+
product: []*capabilitiesv1beta1.Product{getProductCR()},
254254
httpHandlerOptions: []mocks.ApplicationAPIHandlerOpt{
255255
mocks.WithService(3, getApplicationPlanListByProductJson()),
256256
mocks.WithAccount(3, &client.ApplicationList{Applications: []client.ApplicationElem{
@@ -297,8 +297,9 @@ func TestApplicationReconciler_Reconcile(t *testing.T) {
297297
},
298298
},
299299
account: getApplicationDeveloperAccount(),
300-
product: getProductCR(),
300+
product: []*capabilitiesv1beta1.Product{getProductCR()},
301301
httpHandlerOptions: []mocks.ApplicationAPIHandlerOpt{
302+
mocks.WithInitAppID(3),
302303
mocks.WithService(3, getApplicationPlanListByProductJson()),
303304
mocks.WithAccount(3, &client.ApplicationList{Applications: []client.ApplicationElem{
304305
{Application: *getApplicationJson("live")},
@@ -357,7 +358,7 @@ func TestApplicationReconciler_Reconcile(t *testing.T) {
357358
},
358359
},
359360
account: getApplicationDeveloperAccount(),
360-
product: getProductCR(),
361+
product: []*capabilitiesv1beta1.Product{getProductCR()},
361362
httpHandlerOptions: []mocks.ApplicationAPIHandlerOpt{
362363
mocks.WithService(3, getApplicationPlanListByProductJson()),
363364
mocks.WithAccount(3, &client.ApplicationList{Applications: []client.ApplicationElem{
@@ -397,6 +398,111 @@ func TestApplicationReconciler_Reconcile(t *testing.T) {
397398
require.Equal(t, err.Error(), "applications.capabilities.3scale.net \"test\" not found")
398399
},
399400
},
401+
{
402+
name: "Update Product reference successful",
403+
application: &capabilitiesv1beta1.Application{
404+
ObjectMeta: metav1.ObjectMeta{
405+
Name: "test",
406+
Namespace: "test",
407+
},
408+
Spec: capabilitiesv1beta1.ApplicationSpec{
409+
AccountCR: &corev1.LocalObjectReference{
410+
Name: "test",
411+
},
412+
ProductCR: &corev1.LocalObjectReference{
413+
Name: "test",
414+
},
415+
Name: "test",
416+
Description: "test",
417+
ApplicationPlanName: "test",
418+
},
419+
},
420+
account: getApplicationDeveloperAccount(),
421+
product: []*capabilitiesv1beta1.Product{
422+
getProductCR(),
423+
{
424+
TypeMeta: metav1.TypeMeta{},
425+
ObjectMeta: metav1.ObjectMeta{
426+
Name: "test2",
427+
Namespace: "test",
428+
},
429+
Spec: capabilitiesv1beta1.ProductSpec{
430+
Name: "test2",
431+
SystemName: "test2",
432+
Description: "test2",
433+
},
434+
Status: capabilitiesv1beta1.ProductStatus{
435+
ID: ptr.To(int64(4)),
436+
ProviderAccountHost: "some string",
437+
ObservedGeneration: 1,
438+
Conditions: common.Conditions{common.Condition{
439+
Type: capabilitiesv1beta1.ProductSyncedConditionType,
440+
Status: corev1.ConditionTrue,
441+
}},
442+
},
443+
},
444+
},
445+
httpHandlerOptions: []mocks.ApplicationAPIHandlerOpt{
446+
mocks.WithInitAppID(3),
447+
mocks.WithService(3, getApplicationPlanListByProductJson()),
448+
mocks.WithService(4, getApplicationPlanListByProductJson()),
449+
mocks.WithAccount(3, &client.ApplicationList{Applications: []client.ApplicationElem{
450+
{Application: *getApplicationJson("live")},
451+
}}),
452+
},
453+
testBody: func(t *testing.T, r *reconcilers.BaseReconciler, req reconcile.Request) {
454+
ctx := context.Background()
455+
applicationReconciler := ApplicationReconciler{BaseReconciler: r}
456+
_, err := applicationReconciler.Reconcile(ctx, req)
457+
require.NoError(t, err)
458+
459+
t.Log("verifying the Application gets finalizers assigned")
460+
var application capabilitiesv1beta1.Application
461+
require.NoError(t, r.Client().Get(ctx, req.NamespacedName, &application))
462+
require.ElementsMatch(t, application.GetFinalizers(), []string{
463+
applicationFinalizer,
464+
})
465+
466+
// TODO: check owner reference
467+
468+
// need to trigger the Reconcile again because the first one only updated the finalizers
469+
_, err = applicationReconciler.Reconcile(ctx, req)
470+
require.NoError(t, err, "reconciliation returned an error")
471+
// need to trigger the Reconcile again because the previous updated the Status
472+
_, err = applicationReconciler.Reconcile(ctx, req)
473+
require.NoError(t, err, "reconciliation returned an error")
474+
475+
var currentApplication capabilitiesv1beta1.Application
476+
require.NoError(t, r.Client().Get(context.Background(), req.NamespacedName, &currentApplication))
477+
478+
// Check status ID
479+
require.Equal(t, currentApplication.Status.ID, ptr.To(int64(3)))
480+
// check annotation
481+
require.Equal(t, currentApplication.Annotations[applicationIdAnnotation], "3")
482+
// Check condition
483+
condition := currentApplication.Status.Conditions.GetCondition(capabilitiesv1beta1.ApplicationReadyConditionType)
484+
require.Equal(t, corev1.ConditionTrue, condition.Status)
485+
486+
// Update productReference
487+
currentApplication.Spec.ProductCR.Name = "test2"
488+
require.NoError(t, r.Client().Update(context.Background(), &currentApplication))
489+
490+
_, err = applicationReconciler.Reconcile(ctx, req)
491+
require.NoError(t, err, "reconciliation returned an error")
492+
493+
// AppID should increase to 4
494+
require.NoError(t, r.Client().Get(context.Background(), req.NamespacedName, &currentApplication))
495+
require.Equal(t, currentApplication.Status.ID, ptr.To(int64(4)))
496+
condition = currentApplication.Status.Conditions.GetCondition(capabilitiesv1beta1.ApplicationReadyConditionType)
497+
require.Equal(t, corev1.ConditionTrue, condition.Status)
498+
499+
// Reconcile one more time and check annotation
500+
_, err = applicationReconciler.Reconcile(ctx, req)
501+
require.NoError(t, err, "reconciliation returned an error")
502+
require.NoError(t, r.Client().Get(context.Background(), req.NamespacedName, &currentApplication))
503+
require.Equal(t, currentApplication.Annotations[applicationIdAnnotation], "4")
504+
},
505+
},
400506
}
401507

402508
for _, tc := range testCases {
@@ -409,7 +515,9 @@ func TestApplicationReconciler_Reconcile(t *testing.T) {
409515
objectsToAdd = append(objectsToAdd, tc.account)
410516
}
411517
if tc.product != nil {
412-
objectsToAdd = append(objectsToAdd, tc.product)
518+
for _, product := range tc.product {
519+
objectsToAdd = append(objectsToAdd, product)
520+
}
413521
}
414522

415523
if tc.httpHandlerOptions != nil {

controllers/capabilities/application_threescale_reconciler.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,27 @@ func (t *ApplicationThreescaleReconciler) syncApplication(_ any) error {
118118
if err != nil {
119119
return fmt.Errorf("error sync application [%s]: %w", t.applicationResource.Spec.Name, err)
120120
}
121+
application = &a
122+
t.applicationResource.Status.ID = &application.ID
123+
} else if application.ServiceID != t.productID {
124+
// Product reference has changed
125+
err := t.threescaleAPIClient.DeleteApplication(t.accountID, application.ID)
126+
if err != nil {
127+
return fmt.Errorf("failed to delete application [%s] id:[%d] productID:[%d] - err: %w", t.applicationResource.Spec.Name, application.ID, application.ServiceID, err)
128+
}
129+
130+
// Recreate the application
131+
params := threescaleapi.Params{
132+
"name": t.applicationResource.Spec.Name,
133+
"description": t.applicationResource.Spec.Description,
134+
}
135+
136+
// Application doesn't exist yet - create it
137+
a, err := t.threescaleAPIClient.CreateApplication(t.accountID, plan.Element.ID, t.applicationResource.Spec.Name, params)
138+
if err != nil {
139+
return fmt.Errorf("reconcile3scaleApplication application [%s]: %w", t.applicationResource.Spec.Name, err)
140+
}
141+
121142
application = &a
122143
t.applicationResource.Status.ID = &application.ID
123144
}

controllers/capabilities/mocks/application_api_handler.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ import (
1212
)
1313

1414
type ApplicationAPIHandler struct {
15-
mux *http.ServeMux
16-
services map[int64]*client.ApplicationPlanJSONList
17-
accounts map[int64]*client.ApplicationList
15+
mux *http.ServeMux
16+
appIDCount int64
17+
services map[int64]*client.ApplicationPlanJSONList
18+
accounts map[int64]*client.ApplicationList
1819
}
1920

2021
type ApplicationAPIHandlerOpt func(h *ApplicationAPIHandler)
@@ -31,6 +32,12 @@ func WithAccount(account int64, applications *client.ApplicationList) Applicatio
3132
}
3233
}
3334

35+
func WithInitAppID(appID int64) ApplicationAPIHandlerOpt {
36+
return func(m *ApplicationAPIHandler) {
37+
m.appIDCount = appID
38+
}
39+
}
40+
3441
func NewApplicationAPIHandler(opts ...ApplicationAPIHandlerOpt) *ApplicationAPIHandler {
3542
handler := &ApplicationAPIHandler{
3643
accounts: make(map[int64]*client.ApplicationList),
@@ -139,7 +146,7 @@ func (m *ApplicationAPIHandler) applicationHandler(w http.ResponseWriter, r *htt
139146
}
140147

141148
application := client.Application{
142-
ID: 3,
149+
ID: m.appIDCount,
143150
State: "live",
144151
UserAccountID: accountID,
145152
ServiceID: serviceID,
@@ -148,6 +155,9 @@ func (m *ApplicationAPIHandler) applicationHandler(w http.ResponseWriter, r *htt
148155
Description: r.FormValue("description"),
149156
}
150157

158+
// Increase appIDCount
159+
m.appIDCount++
160+
151161
elem := client.ApplicationElem{Application: application}
152162

153163
applications.Applications = append(applications.Applications, elem)

doc/operator-application-capabilities.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,6 +1645,7 @@ Notes:
16451645

16461646
* 3scale applications belong to some DeveloperAccount account.
16471647
* 3scale applications are linked directly to a product and applicationPlan
1648+
* Once set, you cannot modify the accountCR references. If you need to change the account reference, create a new ApplicationCR instead.
16481649

16491650
Consider we have the following product which is connected to a backend
16501651
```yaml

0 commit comments

Comments
 (0)