fix bug: When the Component contains multiple traits of the same type, the status of the trait in the Application is reported incorrectly (#1731)

* fix status

* add test

* fix webhook

* add paramter context for customStatus
This commit is contained in:
yangsoon
2021-05-31 18:44:15 +08:00
committed by GitHub
parent fa8b3ef763
commit 931ca3ea3e
7 changed files with 156 additions and 18 deletions
+2 -2
View File
@@ -96,7 +96,7 @@ func (wl *Workload) EvalContext(ctx process.Context) error {
// EvalStatus eval workload status
func (wl *Workload) EvalStatus(ctx process.Context, cli client.Client, ns string) (string, error) {
return wl.engine.Status(ctx, cli, ns, wl.FullTemplate.CustomStatus)
return wl.engine.Status(ctx, cli, ns, wl.FullTemplate.CustomStatus, wl.Params)
}
// EvalHealth eval workload health check
@@ -149,7 +149,7 @@ func (trait *Trait) EvalContext(ctx process.Context) error {
// EvalStatus eval trait status
func (trait *Trait) EvalStatus(ctx process.Context, cli client.Client, ns string) (string, error) {
return trait.engine.Status(ctx, cli, ns, trait.CustomStatusFormat)
return trait.engine.Status(ctx, cli, ns, trait.CustomStatusFormat, trait.Params)
}
// EvalHealth eval trait health check
@@ -285,33 +285,32 @@ func (h *appHandler) statusAggregate(appFile *appfile.Appfile) ([]common.Applica
}
}
var traitStatusList []common.ApplicationTraitStatus
for _, tr := range wl.Traits {
if err := tr.EvalContext(pCtx); err != nil {
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, trait=%s, evaluate context error", appFile.Name, wl.Name, tr.Name)
}
}
var traitStatusList []common.ApplicationTraitStatus
for _, trait := range wl.Traits {
var traitStatus = common.ApplicationTraitStatus{
Type: trait.Name,
Type: tr.Name,
Healthy: true,
}
traitHealth, err := trait.EvalHealth(pCtx, h.r, h.app.Namespace)
traitHealth, err := tr.EvalHealth(pCtx, h.r, h.app.Namespace)
if err != nil {
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, trait=%s, check health error", appFile.Name, wl.Name, trait.Name)
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, trait=%s, check health error", appFile.Name, wl.Name, tr.Name)
}
if !traitHealth {
// TODO(wonderflow): we should add a custom way to let the template say why it's unhealthy, only a bool flag is not enough
traitStatus.Healthy = false
healthy = false
}
traitStatus.Message, err = trait.EvalStatus(pCtx, h.r, h.app.Namespace)
traitStatus.Message, err = tr.EvalStatus(pCtx, h.r, h.app.Namespace)
if err != nil {
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, trait=%s, evaluate status message error", appFile.Name, wl.Name, trait.Name)
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, trait=%s, evaluate status message error", appFile.Name, wl.Name, tr.Name)
}
traitStatusList = append(traitStatusList, traitStatus)
}
status.Traits = traitStatusList
status.Scopes = generateScopeReference(wl.Scopes)
appStatus = append(appStatus, status)
+20 -7
View File
@@ -58,7 +58,7 @@ const (
type AbstractEngine interface {
Complete(ctx process.Context, abstractTemplate string, params interface{}) error
HealthCheck(ctx process.Context, cli client.Client, ns string, healthPolicyTemplate string) (bool, error)
Status(ctx process.Context, cli client.Client, ns string, customStatusTemplate string) (string, error)
Status(ctx process.Context, cli client.Client, ns string, customStatusTemplate string, parameter interface{}) (string, error)
}
type def struct {
@@ -223,7 +223,7 @@ func checkHealth(templateContext map[string]interface{}, healthPolicyTemplate st
}
// Status get workload status by customStatusTemplate
func (wd *workloadDef) Status(ctx process.Context, cli client.Client, ns string, customStatusTemplate string) (string, error) {
func (wd *workloadDef) Status(ctx process.Context, cli client.Client, ns string, customStatusTemplate string, parameter interface{}) (string, error) {
if customStatusTemplate == "" {
return "", nil
}
@@ -231,15 +231,28 @@ func (wd *workloadDef) Status(ctx process.Context, cli client.Client, ns string,
if err != nil {
return "", errors.WithMessage(err, "get template context")
}
return getStatusMessage(templateContext, customStatusTemplate)
return getStatusMessage(templateContext, customStatusTemplate, parameter)
}
func getStatusMessage(templateContext map[string]interface{}, customStatusTemplate string) (string, error) {
func getStatusMessage(templateContext map[string]interface{}, customStatusTemplate string, parameter interface{}) (string, error) {
var ctxBuff string
var paramBuff = "parameter: {}\n"
bt, err := json.Marshal(templateContext)
if err != nil {
return "", errors.WithMessage(err, "json marshal template context")
}
var buff = "context: " + string(bt) + "\n" + customStatusTemplate
ctxBuff = "context: " + string(bt) + "\n"
bt, err = json.Marshal(parameter)
if err != nil {
return "", errors.WithMessage(err, "json marshal template parameters")
}
if string(bt) != "null" {
paramBuff = "parameter: " + string(bt) + "\n"
}
var buff = ctxBuff + paramBuff + customStatusTemplate
var r cue.Runtime
inst, err := r.Compile("-", buff)
if err != nil {
@@ -405,7 +418,7 @@ func (td *traitDef) getTemplateContext(ctx process.Context, cli client.Reader, n
}
// Status get trait status by customStatusTemplate
func (td *traitDef) Status(ctx process.Context, cli client.Client, ns string, customStatusTemplate string) (string, error) {
func (td *traitDef) Status(ctx process.Context, cli client.Client, ns string, customStatusTemplate string, parameter interface{}) (string, error) {
if customStatusTemplate == "" {
return "", nil
}
@@ -413,7 +426,7 @@ func (td *traitDef) Status(ctx process.Context, cli client.Client, ns string, cu
if err != nil {
return "", errors.WithMessage(err, "get template context")
}
return getStatusMessage(templateContext, customStatusTemplate)
return getStatusMessage(templateContext, customStatusTemplate, parameter)
}
// HealthCheck address health check for trait
+26 -1
View File
@@ -1003,6 +1003,7 @@ func TestCheckHealth(t *testing.T) {
func TestGetStatus(t *testing.T) {
cases := map[string]struct {
tpContext map[string]interface{}
parameter interface{}
statusTemp string
expMessage string
}{
@@ -1063,9 +1064,33 @@ if len(context.outputs.ingress.status.loadBalancer.ingress) == 0 {
}`,
expMessage: "Visiting URL: example.com, IP: 10.0.0.1",
},
"status use parameter field": {
tpContext: map[string]interface{}{
"outputs": map[string]interface{}{
"test-name": map[string]interface{}{
"spec": map[string]interface{}{
"type": "NodePort",
"clusterIP": "10.0.0.1",
"ports": []interface{}{
map[string]interface{}{
"port": 80,
},
},
},
},
},
},
parameter: map[string]interface{}{
"configInfo": map[string]string{
"name": "test-name",
},
},
statusTemp: `message: parameter.configInfo.name + ".type: " + context.outputs["\(parameter.configInfo.name)"].spec.type`,
expMessage: "test-name.type: NodePort",
},
}
for message, ca := range cases {
gotMessage, err := getStatusMessage(ca.tpContext, ca.statusTemp)
gotMessage, err := getStatusMessage(ca.tpContext, ca.statusTemp, ca.parameter)
assert.NoError(t, err, message)
assert.Equal(t, ca.expMessage, gotMessage, message)
}
+31
View File
@@ -158,6 +158,37 @@ var _ = Describe("Application Normal tests", func() {
verifyWorkloadRunningExpected("myweb", 1, "stefanprodan/podinfo:5.0.2")
})
It("Test app have component with multiple same type traits", func() {
traitDef := new(v1beta1.TraitDefinition)
Expect(common.ReadYamlToObject("testdata/app/trait_config.yaml", traitDef)).Should(BeNil())
traitDef.Namespace = namespaceName
Expect(k8sClient.Create(ctx, traitDef)).Should(BeNil())
By("apply application")
applyApp("app7.yaml")
appName := "test-worker"
By("check application status")
testApp := new(v1beta1.Application)
Eventually(func() error {
err := k8sClient.Get(ctx, client.ObjectKey{Namespace: namespaceName, Name: appName}, testApp)
if err != nil {
return err
}
if len(testApp.Status.Services) != 1 {
return fmt.Errorf("error ComponentStatus number wants %d, actually %d", 1, len(testApp.Status.Services))
}
if len(testApp.Status.Services[0].Traits) != 2 {
return fmt.Errorf("error TraitStatus number wants %d, actually %d", 2, len(testApp.Status.Services[0].Traits))
}
return nil
}, 5*time.Second).Should(BeNil())
By("check trait status")
Expect(testApp.Status.Services[0].Traits[0].Message).Should(Equal("configMap:app-file-html"))
Expect(testApp.Status.Services[0].Traits[1].Message).Should(Equal("secret:app-env-config"))
})
It("Test app have rollout-template false annotation", func() {
By("Apply an application")
var newApp v1beta1.Application
+25
View File
@@ -0,0 +1,25 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: test-worker
spec:
components:
- name: myworker
type: worker
properties:
image: nginx
traits:
- type: config
properties:
kind: "configMap"
name: "app-file-html"
configname: "file"
data:
test: "demo-app"
- type: config
properties:
kind: "secret"
name: "app-env-config"
configname: "env"
data:
test: "TXlQQHNzMTIz"
+45
View File
@@ -0,0 +1,45 @@
# Code generated by KubeVela templates. DO NOT EDIT.
apiVersion: core.oam.dev/v1beta1
kind: TraitDefinition
metadata:
name: config
spec:
status:
customStatus: |-
message: parameter.kind + ":" + context.outputs["\(parameter.configname)"].metadata.name
appliesToWorkloads:
- deployments.apps
podDisruptive: true
schematic:
cue:
template: |-
outputs: "\(parameter.configname)": {
if parameter.kind == "configMap" {
apiVersion: "v1"
kind: "ConfigMap"
metadata: name: parameter.name
data: {
for k, v in parameter.data {
"\(k)": v
}
}
}
if parameter.kind == "secret" {
apiVersion: "v1"
kind: "Secret"
metadata: name: parameter.name
data: {
for k, v in parameter.data {
"\(k)": v
}
}
}
}
parameter: {
kind: string
name: string
configname: string
data: [string]: string
}