mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-18 03:56:36 +00:00
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:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
+25
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user