Feat: modify apply component cue action to support skipWorkload trait (#2167)

fix lint

fix comments
This commit is contained in:
wyike
2021-08-27 13:12:00 +08:00
committed by GitHub
parent 1f6d4d2345
commit cc8a1d3bde
11 changed files with 296 additions and 103 deletions
@@ -0,0 +1,53 @@
apiVersion: core.oam.dev/v1beta1
kind: Application
metadata:
name: rollout-multi-comp
namespace: default
spec:
components:
- name: back-end
externalRevision: backend-v1
type: webservice
properties:
image: nginx:1.21
port: 80
traits:
- type: rollout
properties:
targetSize: 2
# This means to rollout two more replicas in two batches.
rolloutBatches:
- replicas: 1
- replicas: 1
- name: front-end
externalRevision: front-end-v1
type: webservice
properties:
image: node:16.7-alpine3.12
port: 80
traits:
- type: rollout
properties:
targetSize: 4
# This means to rollout two more replicas in two batches.
rolloutBatches:
- replicas: 2
- replicas: 2
workflow:
steps:
# rollout back-end component first, workflow will block until rollout succeed
- name: rollout-back-end
type: rollout-wait-succeed
properties:
appName: rollout-multi-comp
component: back-end
# rollout front-end component
- name: rollout-front-end
type: rollout-wait-succeed
properties:
appName: rollout-multi-comp
component: front-end
@@ -0,0 +1,33 @@
# Code generated by KubeVela templates. DO NOT EDIT. Please edit the original cue file.
# Definition source cue file: vela-templates/definitions/internal/apply-component.cue
apiVersion: core.oam.dev/v1beta1
kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: Apply components and traits for your workflow steps
name: rollout-wait-succeed
namespace: vela-system
spec:
schematic:
cue:
template: |
import (
"vela/op"
)
// apply components and traits
apply: op.#ApplyComponent & {
component: parameter.component
}
parameter: {
// +usage=Declare the name of the application
appName: string
// +usage=Declare the name of the component
component: string
}
wait: op.#ConditionalWait & {
continue: apply.traits["rollout"].value.status.rollingState=="rolloutSucceed"
}
@@ -138,6 +138,9 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedRender, err))
return r.endWithNegativeCondition(ctx, app, condition.ErrorCondition("Render", err))
}
handler.handleCheckManageWorkloadTrait(handler.currentAppRev.Spec.TraitDefinitions, comps)
if err := handler.HandleComponentsRevision(ctx, comps); err != nil {
klog.ErrorS(err, "Failed to handle compoents revision", "application", klog.KObj(app))
r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedRevision, err))
@@ -1839,7 +1839,10 @@ spec:
}
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
appKey := types.NamespacedName{Namespace: ns.Name, Name: app.Name}
testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: appKey})
// first reconcile handle finalizer
testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey})
// second reconcile apply all resources
testutil.ReconcileOnce(reconciler, reconcile.Request{NamespacedName: appKey})
checkApp := &v1beta1.Application{}
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
Expect(checkApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationRunning))
@@ -1848,6 +1851,81 @@ spec:
By("verify targetRevision will be filled with real compRev by context.Revision")
Expect(checkRollout.Spec.TargetRevisionName).Should(BeEquivalentTo(externalRevision))
})
It("Test rollout trait in workflow", func() {
rolloutTdDef, err := yaml.YAMLToJSON([]byte(rolloutTraitDefinition))
Expect(err).Should(BeNil())
rolloutTrait := &v1beta1.TraitDefinition{}
Expect(json.Unmarshal([]byte(rolloutTdDef), rolloutTrait)).Should(BeNil())
wfStepDef, err := yaml.YAMLToJSON([]byte(applyCompWfStepDefinition))
Expect(err).Should(BeNil())
wfStep := &v1beta1.WorkflowStepDefinition{}
Expect(json.Unmarshal([]byte(wfStepDef), wfStep)).Should(BeNil())
ns := corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "app-with-rollout-workflow",
},
}
rolloutTrait.SetNamespace(ns.Name)
wfStep.SetNamespace(ns.Name)
Expect(k8sClient.Create(ctx, &ns)).Should(BeNil())
Expect(k8sClient.Create(ctx, rolloutTrait)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
Expect(k8sClient.Create(ctx, wfStep)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
app := &v1beta1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app-with-rollout-workflow",
Namespace: ns.Name,
},
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{
{
Name: "myweb1",
Type: "worker",
Properties: runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
Traits: []common.ApplicationTrait{
{
Type: "rollout",
Properties: runtime.RawExtension{Raw: []byte(`{}`)},
},
},
},
},
Workflow: &v1beta1.Workflow{
Steps: []v1beta1.WorkflowStep{
{
Name: "apply",
Type: "apply-component",
Properties: runtime.RawExtension{Raw: []byte(`{"component" : "myweb1"}`)},
},
},
},
},
}
Expect(k8sClient.Create(ctx, app)).Should(BeNil())
appKey := types.NamespacedName{Namespace: ns.Name, Name: app.Name}
testutil.ReconcileOnceAfterFinalizer(reconciler, reconcile.Request{NamespacedName: appKey})
checkApp := &v1beta1.Application{}
Expect(k8sClient.Get(ctx, appKey, checkApp)).Should(BeNil())
Expect(checkApp.Status.Phase).Should(BeEquivalentTo(common.ApplicationRunning))
By("verify workflow apply component had apply rollout")
checkRollout := &stdv1alpha1.Rollout{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "myweb1", Namespace: ns.Name}, checkRollout)).Should(BeNil())
By("verify targetRevision will be filled with real compRev by context.ComponentRevName")
Expect(checkRollout.Spec.TargetRevisionName).Should(BeEquivalentTo("myweb1-v1"))
By("verify workflow apply component didn't apply workload")
deploy := &v1.Deployment{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "myweb1", Namespace: ns.Name}, deploy)).Should(util.NotFoundMatcher{})
})
})
const (
@@ -2732,6 +2810,31 @@ spec:
parameter: {
targetRevision: *context.revision|string
}
`
applyCompWfStepDefinition = `
apiVersion: core.oam.dev/v1beta1
kind: WorkflowStepDefinition
metadata:
annotations:
definition.oam.dev/description: Apply components and traits for your workflow steps
name: apply-component
namespace: vela-system
spec:
schematic:
cue:
template: |
import (
"vela/op"
)
// apply components and traits
apply: op.#ApplyComponent & {
component: parameter.component
}
parameter: {
// +usage=Declare the name of the component
component: string
}
`
)
@@ -235,6 +235,26 @@ func (h *AppHandler) aggregateHealthStatus(appFile *appfile.Appfile) ([]common.A
return appStatus, healthy, nil
}
func (h *AppHandler) handleCheckManageWorkloadTrait(traitDefs map[string]v1beta1.TraitDefinition, comps []*types.ComponentManifest) {
manageWorkloadTrait := map[string]bool{}
for traitName, definition := range traitDefs {
if definition.Spec.ManageWorkload {
manageWorkloadTrait[traitName] = true
}
}
if len(manageWorkloadTrait) == 0 {
return
}
for _, comp := range comps {
for _, trait := range comp.Traits {
traitType := trait.GetLabels()[oam.TraitTypeLabel]
if manageWorkloadTrait[traitType] {
trait.SetLabels(oamutil.MergeMapOverrideWithDst(trait.GetLabels(), map[string]string{oam.LabelManageWorkloadTrait: "true"}))
}
}
}
}
func generateScopeReference(scopes []appfile.Scope) []corev1.ObjectReference {
var references []corev1.ObjectReference
for _, scope := range scopes {
@@ -30,6 +30,7 @@ import (
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -40,6 +41,7 @@ import (
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
velatypes "github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/appfile"
"github.com/oam-dev/kubevela/pkg/oam"
)
const workloadDefinition = `
@@ -215,3 +217,37 @@ var _ = Describe("Test statusAggregate", func() {
Expect(err).Should(BeNil())
})
})
var _ = Describe("Test handleCheckManageWorkloadTrait func", func() {
It("Test every situation", func() {
traitDefs := map[string]v1beta1.TraitDefinition{
"rollout": v1beta1.TraitDefinition{
Spec: v1beta1.TraitDefinitionSpec{
ManageWorkload: true,
},
},
"normal": v1beta1.TraitDefinition{
Spec: v1beta1.TraitDefinitionSpec{},
},
}
rolloutTrait := &unstructured.Unstructured{}
rolloutTrait.SetLabels(map[string]string{oam.TraitTypeLabel: "rollout"})
normalTrait := &unstructured.Unstructured{}
normalTrait.SetLabels(map[string]string{oam.TraitTypeLabel: "normal"})
comps := []*velatypes.ComponentManifest{
{
Traits: []*unstructured.Unstructured{
rolloutTrait,
normalTrait,
},
},
}
h := AppHandler{}
h.handleCheckManageWorkloadTrait(traitDefs, comps)
Expect(len(rolloutTrait.GetLabels())).Should(BeEquivalentTo(2))
Expect(rolloutTrait.GetLabels()[oam.LabelManageWorkloadTrait]).Should(BeEquivalentTo("true"))
Expect(len(normalTrait.GetLabels())).Should(BeEquivalentTo(1))
Expect(normalTrait.GetLabels()[oam.LabelManageWorkloadTrait]).Should(BeEquivalentTo(""))
})
})
@@ -108,19 +108,22 @@ func (am *AppManifests) AssembledManifests() ([]*unstructured.Unstructured, erro
if am.err != nil {
return nil, am.err
}
am.CheckSkipApplyWorkloadComp()
r := make([]*unstructured.Unstructured, 0)
for compName, wl := range am.assembledWorkloads {
if !am.skipWorkloadApplyComp[compName] {
skipApplyWorkload := false
ts := am.assembledTraits[compName]
for _, t := range ts {
r = append(r, t.DeepCopy())
if v := t.GetLabels()[oam.LabelManageWorkloadTrait]; v == "true" {
skipApplyWorkload = true
}
}
if !skipApplyWorkload {
r = append(r, wl.DeepCopy())
} else {
klog.InfoS("assemble meet a managedByTrait workload, so skip apply it",
"namespace", am.AppRevision.Namespace, "appRev", am.AppRevision.Name)
}
ts := am.assembledTraits[compName]
for _, t := range ts {
r = append(r, t.DeepCopy())
}
}
return r, nil
}
@@ -414,25 +417,3 @@ func (am *AppManifests) setWorkloadRefToTrait(wlRef corev1.ObjectReference, trai
}
return nil
}
// CheckSkipApplyWorkloadComp check component's workload is manage by trait, if yes skip apply it
func (am *AppManifests) CheckSkipApplyWorkloadComp() {
app := am.AppRevision.Spec.Application
traitDefs := am.AppRevision.Spec.TraitDefinitions
manageWorkloadTrait := map[string]bool{}
for traitName, definition := range traitDefs {
if definition.Spec.ManageWorkload {
manageWorkloadTrait[traitName] = true
}
}
if len(manageWorkloadTrait) == 0 {
return
}
for _, component := range app.Spec.Components {
for _, trait := range component.Traits {
if manageWorkloadTrait[trait.Type] {
am.skipWorkloadApplyComp[component.Name] = true
}
}
}
}
@@ -26,7 +26,6 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/yaml"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/oam"
)
@@ -207,58 +206,3 @@ var _ = Describe("Test Assemble Options", func() {
Expect(annotationKeys).Should(ContainElements("canPassAnno"))
})
})
var _ = Describe("Test CheckSkipApplyWorkloadComp func", func() {
It("Test every situation", func() {
a := AppManifests{
AppRevision: &v1beta1.ApplicationRevision{
Spec: v1beta1.ApplicationRevisionSpec{
Application: v1beta1.Application{
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{
{
Name: "comp1",
Traits: []common.ApplicationTrait{
{
Type: "rollout",
},
{
Type: "normal",
},
},
},
{
Name: "comp2",
Traits: []common.ApplicationTrait{
{
Type: "normal",
},
},
},
{
Name: "comp3",
},
},
},
},
TraitDefinitions: map[string]v1beta1.TraitDefinition{
"rollout": v1beta1.TraitDefinition{
Spec: v1beta1.TraitDefinitionSpec{
ManageWorkload: true,
},
},
"normal": v1beta1.TraitDefinition{
Spec: v1beta1.TraitDefinitionSpec{},
},
},
},
},
skipWorkloadApplyComp: make(map[string]bool),
}
a.CheckSkipApplyWorkloadComp()
Expect(len(a.skipWorkloadApplyComp)).Should(BeEquivalentTo(1))
Expect(a.skipWorkloadApplyComp["comp1"]).Should(BeTrue())
Expect(a.skipWorkloadApplyComp["comp2"]).Should(BeFalse())
Expect(a.skipWorkloadApplyComp["comp3"]).Should(BeFalse())
})
})
@@ -395,6 +395,14 @@ func (h *AppHandler) handleComponentRevisionNameSpecified(ctx context.Context, c
if err := h.createControllerRevision(ctx, comp); err != nil {
return err
}
// when controllerRevision not exist handle replace context.RevisionName
for _, trait := range comp.Traits {
if err := replaceComponentRevisionContext(trait, comp.RevisionName); err != nil {
return err
}
}
return nil
}
+2
View File
@@ -47,6 +47,8 @@ const (
LabelComponentDefinitionName = "componentdefinition.oam.dev/name"
// LabelTraitDefinitionName records the name of TraitDefinition
LabelTraitDefinitionName = "trait.oam.dev/name"
// LabelManageWorkloadTrait indicates if the trait will manage the lifecycle of the workload
LabelManageWorkloadTrait = "trait.oam.dev/manage-workload"
// LabelPolicyDefinitionName records the name of PolicyDefinition
LabelPolicyDefinitionName = "policydefinition.oam.dev/name"
// LabelWorkflowStepDefinitionName records the name of WorkflowStepDefinition
+28 -18
View File
@@ -16,26 +16,36 @@ import (
#Apply: kube.#Apply
#ApplyComponent: #Steps & {
component: string
_componentName: component
load: ws.#Load & {
component: _componentName
} @step(1)
workload: workload__.value
workload__: kube.#Apply & {
value: load.value.workload
...
} @step(2)
component: string
_componentName: component
load: ws.#Load & {
component: _componentName
} @step(1)
traits: #Steps & {
_key: "trait.oam.dev/resource"
_manWlKey: "trait.oam.dev/manage-workload"
skipApplyWorkload: *false | bool
if load.value.auxiliaries != _|_ {
for o in load.value.auxiliaries {
"\(o.metadata.labels[_key])": kube.#Apply & {value: o}
if o.metadata.labels[_manWlKey] != _|_ {
skipApplyWorkload: true
}
}
}
} @step(2)
workload__: {
if !traits.skipApplyWorkload {
kube.#Apply & {
value: load.value.workload
...
}
}
} @step(3)
traits: #Steps & {
_key: "trait.oam.dev/resource"
if load.value.auxiliaries != _|_ {
for o in load.value.auxiliaries {
"\(o.metadata.labels[_key])": kube.#Apply & {value: o}
}
}
} @step(3)
}
#ApplyRemaining: #Steps & {