From e565ff80008158f0d7abd921178bc0e9b7c2e8ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E5=85=83?= Date: Mon, 28 Sep 2020 17:34:39 +0800 Subject: [PATCH] add an unsatisfied reason into dependency status and tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 天元 --- apis/core/v1alpha2/core_types.go | 13 +- ...ore.oam.dev_applicationconfigurations.yaml | 23 ++- .../applicationconfiguration_test.go | 4 + .../applicationconfiguration/render.go | 52 ++--- .../applicationconfiguration/render_test.go | 82 +++++++- test/e2e-test/appconfig_dependency_test.go | 193 +++++++++++++++++- test/e2e-test/suite_test.go | 9 +- 7 files changed, 326 insertions(+), 50 deletions(-) diff --git a/apis/core/v1alpha2/core_types.go b/apis/core/v1alpha2/core_types.go index ae1f5602..d5bd558d 100644 --- a/apis/core/v1alpha2/core_types.go +++ b/apis/core/v1alpha2/core_types.go @@ -416,8 +416,9 @@ type DependencyStatus struct { // UnstaifiedDependency describes unsatisfied dependency flow between // one pair of objects. type UnstaifiedDependency struct { - From DependencyFromObject `json:"from"` - To DependencyToObject `json:"to"` + Reason string `json:"reason"` + From DependencyFromObject `json:"from"` + To DependencyToObject `json:"to"` } // DependencyFromObject represents the object that dependency data comes from. @@ -490,16 +491,20 @@ type ConditionRequirement struct { Operator ConditionOperator `json:"op"` // +optional - // Value is mutually exclusive with ValueFrom + // Value specifies an expected value + // This is mutually exclusive with ValueFrom Value string `json:"value,omitempty"` // +optional + // ValueFrom specifies expected value from AppConfig // This is mutually exclusive with Value ValueFrom ValueFrom `json:"valueFrom,omitempty"` + // +optional + // FieldPath specifies got value from workload/trait object FieldPath string `json:"fieldPath,omitempty"` } -// ValueFrom get value from AppConfig object by specify a path +// ValueFrom gets value from AppConfig object by specifying a path type ValueFrom struct { FieldPath string `json:"fieldPath"` } diff --git a/charts/oam-kubernetes-runtime/crds/core.oam.dev_applicationconfigurations.yaml b/charts/oam-kubernetes-runtime/crds/core.oam.dev_applicationconfigurations.yaml index 9cce6fd5..e32ce03f 100644 --- a/charts/oam-kubernetes-runtime/crds/core.oam.dev_applicationconfigurations.yaml +++ b/charts/oam-kubernetes-runtime/crds/core.oam.dev_applicationconfigurations.yaml @@ -101,16 +101,21 @@ spec: to match a value. properties: fieldPath: + description: FieldPath specifies got value from + workload/trait object type: string op: description: ConditionOperator specifies the operator to match a value. type: string value: - description: Value is mutually exclusive with ValueFrom + description: Value specifies an expected value This + is mutually exclusive with ValueFrom type: string valueFrom: - description: This is mutually exclusive with Value + description: ValueFrom specifies expected value + from AppConfig This is mutually exclusive with + Value properties: fieldPath: type: string @@ -241,18 +246,21 @@ spec: requirement to match a value. properties: fieldPath: + description: FieldPath specifies got value + from workload/trait object type: string op: description: ConditionOperator specifies the operator to match a value. type: string value: - description: Value is mutually exclusive with - ValueFrom + description: Value specifies an expected value + This is mutually exclusive with ValueFrom type: string valueFrom: - description: This is mutually exclusive with - Value + description: ValueFrom specifies expected + value from AppConfig This is mutually exclusive + with Value properties: fieldPath: type: string @@ -356,6 +364,8 @@ spec: - kind - name type: object + reason: + type: string to: description: DependencyToObject represents the object that dependency data goes to. @@ -383,6 +393,7 @@ spec: type: object required: - from + - reason - to type: object type: array diff --git a/pkg/controller/v1alpha2/applicationconfiguration/applicationconfiguration_test.go b/pkg/controller/v1alpha2/applicationconfiguration/applicationconfiguration_test.go index c8a67c91..0fe36111 100644 --- a/pkg/controller/v1alpha2/applicationconfiguration/applicationconfiguration_test.go +++ b/pkg/controller/v1alpha2/applicationconfiguration/applicationconfiguration_test.go @@ -924,6 +924,7 @@ func TestDependency(t *testing.T) { }, depStatus: &v1alpha2.DependencyStatus{ Unsatisfied: []v1alpha2.UnstaifiedDependency{{ + Reason: "status.key not found in object", From: v1alpha2.DependencyFromObject{ TypedReference: runtimev1alpha1.TypedReference{ APIVersion: unreadyWorkload.GetAPIVersion(), @@ -1006,6 +1007,7 @@ func TestDependency(t *testing.T) { }, depStatus: &v1alpha2.DependencyStatus{ Unsatisfied: []v1alpha2.UnstaifiedDependency{{ + Reason: "status.key not found in object", From: v1alpha2.DependencyFromObject{ TypedReference: runtimev1alpha1.TypedReference{ APIVersion: unreadyTrait.GetAPIVersion(), @@ -1091,6 +1093,7 @@ func TestDependency(t *testing.T) { }, depStatus: &v1alpha2.DependencyStatus{ Unsatisfied: []v1alpha2.UnstaifiedDependency{{ + Reason: "status.key not found in object", From: v1alpha2.DependencyFromObject{ TypedReference: runtimev1alpha1.TypedReference{ APIVersion: unreadyWorkload.GetAPIVersion(), @@ -1178,6 +1181,7 @@ func TestDependency(t *testing.T) { }, depStatus: &v1alpha2.DependencyStatus{ Unsatisfied: []v1alpha2.UnstaifiedDependency{{ + Reason: "status.key not found in object", From: v1alpha2.DependencyFromObject{ TypedReference: runtimev1alpha1.TypedReference{ APIVersion: unreadyTrait.GetAPIVersion(), diff --git a/pkg/controller/v1alpha2/applicationconfiguration/render.go b/pkg/controller/v1alpha2/applicationconfiguration/render.go index f788ff1f..9de78df9 100644 --- a/pkg/controller/v1alpha2/applicationconfiguration/render.go +++ b/pkg/controller/v1alpha2/applicationconfiguration/render.go @@ -410,8 +410,9 @@ func (r *components) handleDependency(ctx context.Context, w *Workload, acc v1al return uds, nil } -func makeUnsatisfiedDependency(obj *unstructured.Unstructured, s *dagSource, in v1alpha2.DataInput) v1alpha2.UnstaifiedDependency { +func makeUnsatisfiedDependency(obj *unstructured.Unstructured, s *dagSource, in v1alpha2.DataInput, reason string) v1alpha2.UnstaifiedDependency { return v1alpha2.UnstaifiedDependency{ + Reason: reason, From: v1alpha2.DependencyFromObject{ TypedReference: runtimev1alpha1.TypedReference{ APIVersion: s.ObjectRef.APIVersion, @@ -438,12 +439,12 @@ func (r *components) handleDataInput(ctx context.Context, ins []v1alpha2.DataInp if !ok { return nil, errors.Wrapf(ErrDataOutputNotExist, "DataOutputName (%s)", in.ValueFrom.DataOutputName) } - val, ready, err := r.getDataInput(ctx, s, ac) + val, ready, reason, err := r.getDataInput(ctx, s, ac) if err != nil { return nil, errors.Wrap(err, "getDataInput failed") } if !ready { - uds = append(uds, makeUnsatisfiedDependency(obj, s, in)) + uds = append(uds, makeUnsatisfiedDependency(obj, s, in, reason)) return uds, nil } @@ -483,7 +484,7 @@ func fillValue(obj *unstructured.Unstructured, fs []string, val interface{}) err return nil } -func (r *components) getDataInput(ctx context.Context, s *dagSource, ac *unstructured.Unstructured) (interface{}, bool, error) { +func (r *components) getDataInput(ctx context.Context, s *dagSource, ac *unstructured.Unstructured) (interface{}, bool, string, error) { obj := s.ObjectRef key := types.NamespacedName{ Namespace: obj.Namespace, @@ -493,7 +494,8 @@ func (r *components) getDataInput(ctx context.Context, s *dagSource, ac *unstruc u.SetGroupVersionKind(obj.GroupVersionKind()) err := r.client.Get(ctx, key, u) if err != nil { - return nil, false, errors.Wrap(resource.IgnoreNotFound(err), fmt.Sprintf("failed to get object (%s)", key.String())) + reason := fmt.Sprintf("failed to get object (%s)", key.String()) + return nil, false, reason, errors.Wrap(resource.IgnoreNotFound(err), reason) } paved := fieldpath.Pave(u.UnstructuredContent()) pavedAC := fieldpath.Pave(ac.UnstructuredContent()) @@ -501,35 +503,37 @@ func (r *components) getDataInput(ctx context.Context, s *dagSource, ac *unstruc rawval, err := paved.GetValue(obj.FieldPath) if err != nil { if fieldpath.IsNotFound(err) { - return "", false, nil + return "", false, fmt.Sprintf("%s not found in object", obj.FieldPath), nil } - return nil, false, fmt.Errorf("failed to get field value (%s) in object (%s): %w", obj.FieldPath, key.String(), err) + err = fmt.Errorf("failed to get field value (%s) in object (%s): %w", obj.FieldPath, key.String(), err) + return nil, false, err.Error(), err } var ok bool + var reason string switch val := rawval.(type) { case string: // For string input we will: // - check its value not empty if no condition is given. // - check its value against conditions if no field path is specified. - ok, err = matchValue(s.Conditions, val, paved, pavedAC) + ok, reason = matchValue(s.Conditions, val, paved, pavedAC) default: - ok, err = checkConditions(s.Conditions, paved, nil, pavedAC) - } - if err != nil { - return nil, false, err + ok, reason = checkConditions(s.Conditions, paved, nil, pavedAC) } if !ok { - return nil, false, nil + return nil, false, reason, nil } - return rawval, true, nil + return rawval, true, "", nil } -func matchValue(conds []v1alpha2.ConditionRequirement, val string, paved, ac *fieldpath.Paved) (bool, error) { +func matchValue(conds []v1alpha2.ConditionRequirement, val string, paved, ac *fieldpath.Paved) (bool, string) { // If no condition is specified, it is by default to check value not empty. if len(conds) == 0 { - return val != "", nil + if val == "" { + return false, "value should not be empty" + } + return true, "" } return checkConditions(conds, paved, &val, ac) @@ -558,38 +562,38 @@ func getExpectVal(m v1alpha2.ConditionRequirement, ac *fieldpath.Paved) (string, var err error value, err := ac.GetString(m.ValueFrom.FieldPath) if err != nil { - return "", fmt.Errorf("get field path %s err %v", m.ValueFrom.FieldPath, err) + return "", fmt.Errorf("get valueFrom.fieldPath fail: %v", err) } return value, nil } -func checkConditions(conds []v1alpha2.ConditionRequirement, paved *fieldpath.Paved, val *string, ac *fieldpath.Paved) (bool, error) { +func checkConditions(conds []v1alpha2.ConditionRequirement, paved *fieldpath.Paved, val *string, ac *fieldpath.Paved) (bool, string) { for _, m := range conds { checkVal, err := getCheckVal(m, paved, val) if err != nil { - return false, err + return false, fmt.Sprintf("can't get value to check %v", err) } m.Value, err = getExpectVal(m, ac) if err != nil { - return false, fmt.Errorf("get field path %s err %v", m.ValueFrom.FieldPath, err) + return false, err.Error() } switch m.Operator { case v1alpha2.ConditionEqual: if m.Value != checkVal { - return false, nil + return false, fmt.Sprintf("got(%v) expected to be %v", checkVal, m.Value) } case v1alpha2.ConditionNotEqual: if m.Value == checkVal { - return false, nil + return false, fmt.Sprintf("got(%v) expected not to be %v", checkVal, m.Value) } case v1alpha2.ConditionNotEmpty: if checkVal == "" { - return false, nil + return false, "value should not be empty" } } } - return true, nil + return true, "" } // GetTraitName return trait name diff --git a/pkg/controller/v1alpha2/applicationconfiguration/render_test.go b/pkg/controller/v1alpha2/applicationconfiguration/render_test.go index 3c463306..9437e29e 100644 --- a/pkg/controller/v1alpha2/applicationconfiguration/render_test.go +++ b/pkg/controller/v1alpha2/applicationconfiguration/render_test.go @@ -947,20 +947,46 @@ func TestMatchValue(t *testing.T) { t.Fatal(err) } + ac := &unstructured.Unstructured{} + ac.SetAPIVersion("core.oam.dev/v1alpha2") + ac.SetKind("ApplicationConfiguration") + ac.SetNamespace("test-ns") + ac.SetName("test-app") + if err := unstructured.SetNestedField(ac.Object, "test", "metadata", "labels", "app-hash"); err != nil { + t.Fatal(err) + } + if err := unstructured.SetNestedField(ac.Object, "different", "metadata", "annotations", "app-hash"); err != nil { + t.Fatal(err) + } + if err := unstructured.SetNestedField(ac.Object, int64(123), "metadata", "annotations", "app-int"); err != nil { + t.Fatal(err) + } + pavedAC, err := fieldpath.PaveObject(ac) + if err != nil { + t.Fatal(err) + } + type args struct { conds []v1alpha2.ConditionRequirement val string paved *fieldpath.Paved + ac *fieldpath.Paved } type want struct { matched bool + reason string } cases := map[string]struct { args args want want }{ - "No conditions with nonempty value should match": {}, "No conditions with empty value should not match": { + want: want{ + matched: false, + reason: "value should not be empty", + }, + }, + "No conditions with nonempty value should match": { args: args{ val: "test", }, @@ -990,6 +1016,7 @@ func TestMatchValue(t *testing.T) { }, want: want{ matched: false, + reason: "got(different) expected to be test", }, }, "notEq condition with different value should match": { @@ -1014,6 +1041,7 @@ func TestMatchValue(t *testing.T) { }, want: want{ matched: false, + reason: "got(test) expected not to be test", }, }, "notEmpty condition with nonempty value should match": { @@ -1036,6 +1064,7 @@ func TestMatchValue(t *testing.T) { }, want: want{ matched: false, + reason: "value should not be empty", }, }, "eq condition with same value from FieldPath should match": { @@ -1062,19 +1091,62 @@ func TestMatchValue(t *testing.T) { }, want: want{ matched: false, + reason: "got(test) expected to be different", + }, + }, + "eq condition with same value from FieldPath and valueFrom AppConfig should not match": { + args: args{ + conds: []v1alpha2.ConditionRequirement{{ + Operator: v1alpha2.ConditionEqual, + ValueFrom: v1alpha2.ValueFrom{FieldPath: "metadata.labels.app-hash"}, + FieldPath: "key", + }}, + paved: paved, + ac: pavedAC, + }, + want: want{ + matched: true, + }, + }, + "eq condition with different value from FieldPath and valueFrom AppConfig should not match": { + args: args{ + conds: []v1alpha2.ConditionRequirement{{ + Operator: v1alpha2.ConditionEqual, + ValueFrom: v1alpha2.ValueFrom{FieldPath: "metadata.annotations.app-hash"}, + FieldPath: "key", + }}, + paved: paved, + ac: pavedAC, + }, + want: want{ + matched: false, + reason: "got(test) expected to be different", + }, + }, + "only string type is supported": { + args: args{ + conds: []v1alpha2.ConditionRequirement{{ + Operator: v1alpha2.ConditionEqual, + ValueFrom: v1alpha2.ValueFrom{FieldPath: "metadata.annotations.app-int"}, + FieldPath: "key", + }}, + paved: paved, + ac: pavedAC, + }, + want: want{ + matched: false, + reason: "get valueFrom.fieldPath fail: metadata.annotations.app-int: not a string", }, }, } for name, tc := range cases { t.Run(name, func(t *testing.T) { - matched, err := matchValue(tc.args.conds, tc.args.val, tc.args.paved, nil) - if err != nil { - t.Fatal(err) - } + matched, reason := matchValue(tc.args.conds, tc.args.val, tc.args.paved, tc.args.ac) if diff := cmp.Diff(tc.want.matched, matched); diff != "" { t.Error(diff) } + assert.Equal(t, tc.want.reason, reason) }) } } diff --git a/test/e2e-test/appconfig_dependency_test.go b/test/e2e-test/appconfig_dependency_test.go index 9d4240fe..7dc290bc 100644 --- a/test/e2e-test/appconfig_dependency_test.go +++ b/test/e2e-test/appconfig_dependency_test.go @@ -130,7 +130,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() { }) // common function for verification - verify := func(appConfigName string) { + verify := func(appConfigName, reason string) { // Verification before satisfying dependency By("Checking that resource which accepts data isn't created yet") inFooKey := client.ObjectKey{ @@ -143,7 +143,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() { func() error { return k8sClient.Get(ctx, inFooKey, inFoo) }, - time.Second*60, time.Second*5).Should(&util.NotFoundMatcher{}) + time.Second*60, time.Second*2).Should(&util.NotFoundMatcher{}) By("Checking that resource which provides data is created") outFooKey := client.ObjectKey{ Name: outName, @@ -155,7 +155,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() { func() error { return k8sClient.Get(ctx, outFooKey, outFoo) }, - time.Second*60, time.Second*5).Should(BeNil()) + time.Second*60, time.Second*2).Should(BeNil()) By("Verify the appconfig's dependency is unsatisfied") appconfigKey := client.ObjectKey{ Name: appConfigName, @@ -165,6 +165,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() { depStatus := v1alpha2.DependencyStatus{ Unsatisfied: []v1alpha2.UnstaifiedDependency{ { + Reason: reason, From: v1alpha2.DependencyFromObject{ TypedReference: v1alpha1.TypedReference{ APIVersion: tempFoo.GetAPIVersion(), @@ -192,7 +193,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() { k8sClient.Get(ctx, appconfigKey, appconfig) return appconfig.Status.Dependency }, - time.Second*60, time.Second*5).Should(Equal(depStatus)) + time.Second*60, time.Second*2).Should(Equal(depStatus)) // fill value to fieldPath err := unstructured.SetNestedField(outFoo.Object, "test", "status", "key") Expect(err).Should(BeNil()) @@ -204,7 +205,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() { func() error { return k8sClient.Get(ctx, inFooKey, inFoo) }, - time.Second*80, time.Second*5).Should(BeNil()) + time.Second*80, time.Second*2).Should(BeNil()) By("Verify the appconfig's dependency is satisfied") appconfig = &v1alpha2.ApplicationConfiguration{} logf.Log.Info("Checking on appconfig", "Key", appconfigKey) @@ -213,7 +214,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() { k8sClient.Get(ctx, appconfigKey, appconfig) return appconfig.Status.Dependency.Unsatisfied }, - time.Second*80, time.Second*5).Should(BeNil()) + time.Second*80, time.Second*2).Should(BeNil()) } It("trait depends on another trait", func() { @@ -281,7 +282,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() { } logf.Log.Info("Creating application config", "Name", appConfig.Name, "Namespace", appConfig.Namespace) Expect(k8sClient.Create(ctx, &appConfig)).Should(BeNil()) - verify(appConfigName) + verify(appConfigName, "status.key not found in object") }) It("component depends on another component", func() { @@ -321,7 +322,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() { } logf.Log.Info("Creating application config", "Name", appConfig.Name, "Namespace", appConfig.Namespace) Expect(k8sClient.Create(ctx, &appConfig)).Should(BeNil()) - verify(appConfigName) + verify(appConfigName, "status.key not found in object") }) It("component depends on trait", func() { @@ -365,7 +366,7 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() { } logf.Log.Info("Creating application config", "Name", appConfig.Name, "Namespace", appConfig.Namespace) Expect(k8sClient.Create(ctx, &appConfig)).Should(BeNil()) - verify(appConfigName) + verify(appConfigName, "status.key not found in object") }) It("trait depends on component", func() { @@ -409,6 +410,178 @@ var _ = Describe("Resource Dependency in an ApplicationConfiguration", func() { } logf.Log.Info("Creating application config", "Name", appConfig.Name, "Namespace", appConfig.Namespace) Expect(k8sClient.Create(ctx, &appConfig)).Should(BeNil()) - verify(appConfigName) + verify(appConfigName, "status.key not found in object") + }) + + It("component depends on trait with updated condition", func() { + label := map[string]string{"trait": "component", "app-hash": "hash-v1"} + // Create application configuration + appConfigName := "appconfig-trait-comp" + appConfig := v1alpha2.ApplicationConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: appConfigName, + Namespace: namespace, + Labels: label, + }, + Spec: v1alpha2.ApplicationConfigurationSpec{ + Components: []v1alpha2.ApplicationConfigurationComponent{ + { + ComponentName: componentInName, + DataInputs: []v1alpha2.DataInput{ + { + ValueFrom: v1alpha2.DataInputValueFrom{ + DataOutputName: "trait-comp", + }, + ToFieldPaths: []string{"spec.key"}, + }, + }, + Traits: []v1alpha2.ComponentTrait{ + { + Trait: runtime.RawExtension{ + Object: out, + }, + DataOutputs: []v1alpha2.DataOutput{ + { + Name: "trait-comp", + FieldPath: "status.key", + Conditions: []v1alpha2.ConditionRequirement{ + { + Operator: v1alpha2.ConditionEqual, + ValueFrom: v1alpha2.ValueFrom{FieldPath: "metadata.labels.app-hash"}, + FieldPath: "status.app-hash", + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + logf.Log.Info("Creating application config", "Name", appConfig.Name, "Namespace", appConfig.Namespace) + Expect(k8sClient.Create(ctx, &appConfig)).Should(BeNil()) + By("Checking that resource which accepts data isn't created yet") + inFooKey := client.ObjectKey{ + Name: inName, + Namespace: namespace, + } + inFoo := tempFoo.DeepCopy() + logf.Log.Info("Checking on resource that inputs data", "Key", inFooKey) + Eventually( + func() error { + return k8sClient.Get(ctx, inFooKey, inFoo) + }, + time.Second*60, time.Second*2).Should(&util.NotFoundMatcher{}) + + By("Checking that resource which provides data is created") + // Originally the trait has value in `status.key`, but the hash label is old + outFooKey := client.ObjectKey{ + Name: outName, + Namespace: namespace, + } + outFoo := tempFoo.DeepCopy() + Eventually( + func() error { + return k8sClient.Get(ctx, outFooKey, outFoo) + }, + time.Second*60, time.Second*2).Should(BeNil()) + err := unstructured.SetNestedField(outFoo.Object, "test", "status", "key") + Expect(err).Should(BeNil()) + err = unstructured.SetNestedField(outFoo.Object, "hash-v1", "status", "app-hash") + Expect(err).Should(BeNil()) + Expect(k8sClient.Update(ctx, outFoo)).Should(Succeed()) + + appconfigKey := client.ObjectKey{ + Name: appConfigName, + Namespace: namespace, + } + newAppConfig := &v1alpha2.ApplicationConfiguration{} + + // Verification after satisfying dependency + By("Verify the appconfig's dependency is satisfied") + newAppConfig = &v1alpha2.ApplicationConfiguration{} + logf.Log.Info("Checking on appconfig", "Key", appconfigKey) + Eventually( + func() []v1alpha2.UnstaifiedDependency { + var tempApp = &v1alpha2.ApplicationConfiguration{} + k8sClient.Get(ctx, appconfigKey, tempApp) + tempApp.DeepCopyInto(newAppConfig) + return tempApp.Status.Dependency.Unsatisfied + }, + time.Second*80, time.Second*2).Should(BeNil()) + By("Checking that resource which accepts data is created now") + logf.Log.Info("Checking on resource that inputs data", "Key", inFooKey) + Eventually( + func() error { + return k8sClient.Get(ctx, inFooKey, inFoo) + }, + time.Second*80, time.Second*2).Should(BeNil()) + + newAppConfig.Labels["app-hash"] = "hash-v2" + Expect(k8sClient.Update(ctx, newAppConfig)).Should(BeNil()) + + By("Verify the appconfig's dependency should be unsatisfied, because requirementCondition valueFrom not match") + + depStatus := v1alpha2.DependencyStatus{ + Unsatisfied: []v1alpha2.UnstaifiedDependency{ + { + Reason: "got(hash-v1) expected to be hash-v2", + From: v1alpha2.DependencyFromObject{ + TypedReference: v1alpha1.TypedReference{ + APIVersion: tempFoo.GetAPIVersion(), + Name: outName, + Kind: tempFoo.GetKind(), + }, + FieldPath: "status.key", + }, + To: v1alpha2.DependencyToObject{ + TypedReference: v1alpha1.TypedReference{ + APIVersion: tempFoo.GetAPIVersion(), + Name: inName, + Kind: tempFoo.GetKind(), + }, + FieldPaths: []string{ + "spec.key", + }, + }, + }, + }, + } + logf.Log.Info("Checking on appconfig", "Key", appconfigKey) + Eventually( + func() v1alpha2.DependencyStatus { + k8sClient.Get(ctx, appconfigKey, newAppConfig) + return newAppConfig.Status.Dependency + }, + time.Second*60, time.Second*2).Should(Equal(depStatus)) + + // Update trait object + k8sClient.Get(ctx, outFooKey, outFoo) // Get the latest before update + err = unstructured.SetNestedField(outFoo.Object, "test-new", "status", "key") + Expect(err).Should(BeNil()) + err = unstructured.SetNestedField(outFoo.Object, "hash-v2", "status", "app-hash") + Expect(err).Should(BeNil()) + Expect(k8sClient.Update(ctx, outFoo)).Should(Succeed()) + + // Verification after satisfying dependency + By("Checking that resource which accepts data is updated now") + logf.Log.Info("Checking on resource that inputs data", "Key", inFooKey) + Eventually( + func() string { + k8sClient.Get(ctx, inFooKey, inFoo) + outdata, _, _ := unstructured.NestedString(inFoo.Object, "spec", "key") + return outdata + }, + time.Second*80, time.Second*2).Should(Equal("test-new")) + By("Verify the appconfig's dependency is satisfied") + logf.Log.Info("Checking on appconfig", "Key", appconfigKey) + Eventually( + func() []v1alpha2.UnstaifiedDependency { + tempAppConfig := &v1alpha2.ApplicationConfiguration{} + k8sClient.Get(ctx, appconfigKey, tempAppConfig) + return tempAppConfig.Status.Dependency.Unsatisfied + }, + time.Second*80, time.Second*2).Should(BeNil()) }) }) diff --git a/test/e2e-test/suite_test.go b/test/e2e-test/suite_test.go index bfd1ca17..0c2d9523 100644 --- a/test/e2e-test/suite_test.go +++ b/test/e2e-test/suite_test.go @@ -202,12 +202,19 @@ var _ = BeforeSuite(func(done Done) { OpenAPIV3Schema: &crdv1.JSONSchemaProps{ Type: "object", Properties: map[string]crdv1.JSONSchemaProps{ - "status": { + "spec": { Type: "object", Properties: map[string]crdv1.JSONSchemaProps{ "key": {Type: "string"}, }, }, + "status": { + Type: "object", + Properties: map[string]crdv1.JSONSchemaProps{ + "key": {Type: "string"}, + "app-hash": {Type: "string"}, + }, + }, }, }, },