Feat: add render component and apply component remaining (#2587)

* Feat: add render component and apply component remaining

* fix ut

* fix e2e
This commit is contained in:
Tianxin Dong
2021-11-02 19:22:27 +08:00
committed by GitHub
parent 7ba10d6864
commit 637ec6273f
7 changed files with 325 additions and 86 deletions
@@ -169,7 +169,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu
r.Recorder.Event(app, event.Normal(velatypes.ReasonRendered, velatypes.MessageRendered))
if !appWillRollout(app) {
steps, err := handler.GenerateApplicationSteps(ctx, app, appParser, appFile, handler.currentAppRev, r.Client, r.dm, r.pd)
steps, err := handler.GenerateApplicationSteps(ctx, app, appParser, appFile, handler.currentAppRev)
if err != nil {
klog.Error(err, "[handle workflow]")
r.Recorder.Event(app, event.Warning(velatypes.ReasonFailedWorkflow, err))
@@ -30,9 +30,7 @@ import (
"github.com/oam-dev/kubevela/pkg/appfile"
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/assemble"
"github.com/oam-dev/kubevela/pkg/cue/model/value"
"github.com/oam-dev/kubevela/pkg/cue/packages"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
"github.com/oam-dev/kubevela/pkg/oam/util"
"github.com/oam-dev/kubevela/pkg/utils"
"github.com/oam-dev/kubevela/pkg/workflow/providers"
@@ -48,15 +46,12 @@ func (h *AppHandler) GenerateApplicationSteps(ctx context.Context,
app *v1beta1.Application,
appParser *appfile.Parser,
af *appfile.Appfile,
appRev *v1beta1.ApplicationRevision,
cli client.Client,
dm discoverymapper.DiscoveryMapper,
pd *packages.PackageDiscover) ([]wfTypes.TaskRunner, error) {
appRev *v1beta1.ApplicationRevision) ([]wfTypes.TaskRunner, error) {
handlerProviders := providers.NewProviders()
kube.Install(handlerProviders, cli, h.Dispatch)
kube.Install(handlerProviders, h.r.Client, h.Dispatch)
oamProvider.Install(handlerProviders, app, h.applyComponentFunc(
appParser, appRev, af, cli))
taskDiscover := tasks.NewTaskDiscover(handlerProviders, pd, cli, dm)
appParser, appRev, af), h.renderComponentFunc(appParser, appRev, af))
taskDiscover := tasks.NewTaskDiscover(handlerProviders, h.r.pd, h.r.Client, h.r.dm)
var tasks []wfTypes.TaskRunner
for _, step := range af.WorkflowSteps {
options := &wfTypes.GeneratorOptions{
@@ -123,39 +118,34 @@ func convertStepProperties(step *v1beta1.WorkflowStep, app *v1beta1.Application)
return errors.Errorf("component %s not found", o.Component)
}
func (h *AppHandler) applyComponentFunc(appParser *appfile.Parser, appRev *v1beta1.ApplicationRevision, af *appfile.Appfile, cli client.Client) oamProvider.ComponentApply {
func (h *AppHandler) renderComponentFunc(appParser *appfile.Parser, appRev *v1beta1.ApplicationRevision, af *appfile.Appfile) oamProvider.ComponentRender {
return func(comp common.ApplicationComponent, patcher *value.Value, clusterName string, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, error) {
ctx := multicluster.ContextWithClusterName(context.Background(), clusterName)
_, manifest, err := h.prepareWorkloadAndManifests(ctx, appParser, comp, appRev, patcher, af)
if err != nil {
return nil, nil, err
}
return renderComponentsAndTraits(h.r.Client, manifest, appRev, overrideNamespace)
}
}
func (h *AppHandler) applyComponentFunc(appParser *appfile.Parser, appRev *v1beta1.ApplicationRevision, af *appfile.Appfile) oamProvider.ComponentApply {
return func(comp common.ApplicationComponent, patcher *value.Value, clusterName string, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, bool, error) {
ctx := multicluster.ContextWithClusterName(context.Background(), clusterName)
wl, err := appParser.ParseWorkloadFromRevision(comp, appRev)
wl, manifest, err := h.prepareWorkloadAndManifests(ctx, appParser, comp, appRev, patcher, af)
if err != nil {
return nil, nil, false, errors.WithMessage(err, "ParseWorkload")
}
wl.Patch = patcher
manifest, err := af.GenerateComponentManifest(wl)
if err != nil {
return nil, nil, false, errors.WithMessage(err, "GenerateComponentManifest")
}
if err := af.SetOAMContract(manifest); err != nil {
return nil, nil, false, errors.WithMessage(err, "SetOAMContract")
}
if err := h.HandleComponentsRevision(ctx, []*types.ComponentManifest{manifest}); err != nil {
return nil, nil, false, errors.WithMessage(err, "HandleComponentsRevision")
return nil, nil, false, err
}
if len(manifest.PackagedWorkloadResources) != 0 {
if err := h.Dispatch(ctx, clusterName, common.WorkflowResourceCreator, manifest.PackagedWorkloadResources...); err != nil {
return nil, nil, false, errors.WithMessage(err, "cannot dispatch packaged workload resources")
}
}
readyWorkload, readyTraits, err := assemble.PrepareBeforeApply(manifest, appRev, []assemble.WorkloadOption{assemble.DiscoveryHelmBasedWorkload(context.TODO(), h.r.Client)})
readyWorkload, readyTraits, err := renderComponentsAndTraits(h.r.Client, manifest, appRev, overrideNamespace)
if err != nil {
return nil, nil, false, errors.WithMessage(err, "assemble resources before apply fail")
}
if overrideNamespace != "" {
readyWorkload.SetNamespace(overrideNamespace)
for _, readyTrait := range readyTraits {
readyTrait.SetNamespace(overrideNamespace)
}
return nil, nil, false, err
}
skipStandardWorkload := skipApplyWorkload(wl)
if !skipStandardWorkload {
@@ -176,11 +166,50 @@ func (h *AppHandler) applyComponentFunc(appParser *appfile.Parser, appRev *v1bet
if !isHealth {
return nil, nil, false, nil
}
workload, traits, err := getComponentResources(ctx, manifest, skipStandardWorkload, cli)
workload, traits, err := getComponentResources(ctx, manifest, skipStandardWorkload, h.r.Client)
return workload, traits, true, err
}
}
func (h *AppHandler) prepareWorkloadAndManifests(ctx context.Context,
appParser *appfile.Parser,
comp common.ApplicationComponent,
appRev *v1beta1.ApplicationRevision,
patcher *value.Value,
af *appfile.Appfile) (*appfile.Workload, *types.ComponentManifest, error) {
wl, err := appParser.ParseWorkloadFromRevision(comp, appRev)
if err != nil {
return nil, nil, errors.WithMessage(err, "ParseWorkload")
}
wl.Patch = patcher
manifest, err := af.GenerateComponentManifest(wl)
if err != nil {
return nil, nil, errors.WithMessage(err, "GenerateComponentManifest")
}
if err := af.SetOAMContract(manifest); err != nil {
return nil, nil, errors.WithMessage(err, "SetOAMContract")
}
if err := h.HandleComponentsRevision(ctx, []*types.ComponentManifest{manifest}); err != nil {
return nil, nil, errors.WithMessage(err, "HandleComponentsRevision")
}
return wl, manifest, nil
}
func renderComponentsAndTraits(client client.Client, manifest *types.ComponentManifest, appRev *v1beta1.ApplicationRevision, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, error) {
readyWorkload, readyTraits, err := assemble.PrepareBeforeApply(manifest, appRev, []assemble.WorkloadOption{assemble.DiscoveryHelmBasedWorkload(context.TODO(), client)})
if err != nil {
return nil, nil, errors.WithMessage(err, "assemble resources before apply fail")
}
if overrideNamespace != "" {
readyWorkload.SetNamespace(overrideNamespace)
for _, readyTrait := range readyTraits {
readyTrait.SetNamespace(overrideNamespace)
}
}
return readyWorkload, readyTraits, nil
}
func skipApplyWorkload(wl *appfile.Workload) bool {
for _, trait := range wl.Traits {
if trait.FullTemplate.TraitDefinition.Spec.ManageWorkload {
@@ -30,8 +30,6 @@ import (
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
oamcore "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"github.com/oam-dev/kubevela/pkg/cue/packages"
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
@@ -112,10 +110,6 @@ var _ = Describe("Test Application workflow generator", func() {
_, err = af.PrepareWorkflowAndPolicy()
Expect(err).Should(BeNil())
appRev := &oamcore.ApplicationRevision{}
dm, err := discoverymapper.New(cfg)
Expect(err).To(BeNil())
pd, err := packages.NewPackageDiscover(cfg)
Expect(err).To(BeNil())
handler := &AppHandler{
r: reconciler,
@@ -123,7 +117,7 @@ var _ = Describe("Test Application workflow generator", func() {
parser: appParser,
}
taskRunner, err := handler.GenerateApplicationSteps(ctx, app, appParser, af, appRev, k8sClient, dm, pd)
taskRunner, err := handler.GenerateApplicationSteps(ctx, app, appParser, af, appRev)
Expect(err).To(BeNil())
Expect(len(taskRunner)).Should(BeEquivalentTo(2))
Expect(taskRunner[0].Name()).Should(BeEquivalentTo("myweb1"))
@@ -160,10 +154,6 @@ var _ = Describe("Test Application workflow generator", func() {
_, err = af.PrepareWorkflowAndPolicy()
Expect(err).Should(BeNil())
appRev := &oamcore.ApplicationRevision{}
dm, err := discoverymapper.New(cfg)
Expect(err).To(BeNil())
pd, err := packages.NewPackageDiscover(cfg)
Expect(err).To(BeNil())
handler := &AppHandler{
r: reconciler,
@@ -171,10 +161,92 @@ var _ = Describe("Test Application workflow generator", func() {
parser: appParser,
}
taskRunner, err := handler.GenerateApplicationSteps(ctx, app, appParser, af, appRev, k8sClient, dm, pd)
taskRunner, err := handler.GenerateApplicationSteps(ctx, app, appParser, af, appRev)
Expect(err).To(BeNil())
Expect(len(taskRunner)).Should(BeEquivalentTo(2))
Expect(taskRunner[0].Name()).Should(BeEquivalentTo("myweb1"))
Expect(taskRunner[1].Name()).Should(BeEquivalentTo("myweb2"))
})
It("Test render component", func() {
cd := &oamcore.ComponentDefinition{}
td := &oamcore.TraitDefinition{}
defJson, err := yaml.YAMLToJSON([]byte(componentDefYaml))
Expect(err).Should(BeNil())
Expect(json.Unmarshal(defJson, cd)).Should(BeNil())
cd.SetNamespace("vela-system")
Expect(k8sClient.Create(ctx, cd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
rolloutTdDef, err := yaml.YAMLToJSON([]byte(rolloutTraitDefinition))
Expect(err).Should(BeNil())
Expect(json.Unmarshal(rolloutTdDef, td)).Should(BeNil())
td.SetNamespace("vela-system")
Expect(k8sClient.Create(ctx, td)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
app := &oamcore.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "core.oam.dev/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app-test",
Namespace: namespaceName,
},
Spec: oamcore.ApplicationSpec{
Components: []common.ApplicationComponent{
{
Name: "myweb1",
Type: "worker",
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
Traits: []common.ApplicationTrait{
{
Type: "rollout",
},
},
},
},
},
}
af, err := appParser.GenerateAppFile(ctx, app)
Expect(err).Should(BeNil())
_, err = af.PrepareWorkflowAndPolicy()
Expect(err).Should(BeNil())
apprev := &oamcore.ApplicationRevision{
ObjectMeta: metav1.ObjectMeta{
Name: "app-test-v1",
Namespace: namespaceName,
},
Spec: oamcore.ApplicationRevisionSpec{
Application: *app.DeepCopy(),
ComponentDefinitions: make(map[string]oamcore.ComponentDefinition),
WorkloadDefinitions: make(map[string]oamcore.WorkloadDefinition),
TraitDefinitions: make(map[string]oamcore.TraitDefinition),
ScopeDefinitions: make(map[string]oamcore.ScopeDefinition),
},
}
apprev.Spec.ComponentDefinitions["worker"] = *cd
apprev.Spec.TraitDefinitions["rollout"] = *td
Expect(k8sClient.Create(ctx, apprev)).Should(BeNil())
handler := &AppHandler{
r: reconciler,
app: app,
parser: appParser,
}
renderFunc := handler.renderComponentFunc(appParser, apprev, af)
comp := common.ApplicationComponent{
Name: "myweb1",
Type: "worker",
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
Traits: []common.ApplicationTrait{
{
Type: "rollout",
},
},
}
_, _, err = renderFunc(comp, nil, "", "")
Expect(err).Should(BeNil())
})
})
+26
View File
@@ -36,6 +36,32 @@ import (
#ApplyComponent: oam.#ApplyComponent
#RenderComponent: oam.#RenderComponent
#ApplyComponentRemaining: #Steps & {
// exceptions specify the resources not to apply.
exceptions: [...string]
_exceptions: {for c in exceptions {"\(c)": true}}
component: string
load: oam.#LoadComponets @step(1)
render: #Steps & {
rendered: oam.#RenderComponent & {
value: load.value[component]
}
comp: kube.#Apply & {
value: rendered.output
}
for name, c in rendered.outputs {
if _exceptions[name] == _|_ {
"\(name)": kube.#Apply & {
value: c
}
}
}
} @step(2)
}
#ApplyRemaining: #Steps & {
// exceptions specify the resources not to apply.
exceptions: [...string]
+11
View File
@@ -7,6 +7,17 @@
...
}
#RenderComponent: {
#provider: "oam"
#do: "component-render"
cluster: *"" | string
value: {...}
patch?: {...}
output?: {...}
outputs?: {...}
...
}
#LoadComponets: {
#provider: "oam"
#do: "load"
+69 -23
View File
@@ -41,32 +41,51 @@ const (
// ComponentApply apply oam component.
type ComponentApply func(comp common.ApplicationComponent, patcher *value.Value, clusterName string, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, bool, error)
// ComponentRender render oam component.
type ComponentRender func(comp common.ApplicationComponent, patcher *value.Value, clusterName string, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, error)
type provider struct {
apply ComponentApply
app *v1beta1.Application
render ComponentRender
apply ComponentApply
app *v1beta1.Application
}
// ApplyComponent apply component.
func (p *provider) RenderComponent(ctx wfContext.Context, v *value.Value, act wfTypes.Action) error {
comp, patcher, clusterName, overrideNamespace, err := lookUpValues(v)
if err != nil {
return err
}
workload, traits, err := p.render(*comp, patcher, clusterName, overrideNamespace)
if err != nil {
return err
}
if workload != nil {
if err := v.FillObject(workload.Object, "output"); err != nil {
return errors.WithMessage(err, "FillOutput")
}
}
for _, trait := range traits {
name := trait.GetLabels()[oam.TraitResource]
if name != "" {
if err := v.FillObject(trait.Object, "outputs", name); err != nil {
return errors.WithMessage(err, "FillOutputs")
}
}
}
return nil
}
// ApplyComponent apply component.
func (p *provider) ApplyComponent(ctx wfContext.Context, v *value.Value, act wfTypes.Action) error {
compSettings, err := v.LookupValue("value")
comp, patcher, clusterName, overrideNamespace, err := lookUpValues(v)
if err != nil {
return err
}
comp := common.ApplicationComponent{}
if err := compSettings.UnmarshalTo(&comp); err != nil {
return err
}
patcher, _ := v.LookupValue("patch")
clusterName, err := v.GetString("cluster")
if err != nil {
clusterName = ""
}
overrideNamespace, err := v.GetString("namespace")
if err != nil {
overrideNamespace = ""
}
workload, traits, healthy, err := p.apply(comp, patcher, clusterName, overrideNamespace)
workload, traits, healthy, err := p.apply(*comp, patcher, clusterName, overrideNamespace)
if err != nil {
return err
}
@@ -93,6 +112,31 @@ func (p *provider) ApplyComponent(ctx wfContext.Context, v *value.Value, act wfT
return nil
}
func lookUpValues(v *value.Value) (*common.ApplicationComponent, *value.Value, string, string, error) {
compSettings, err := v.LookupValue("value")
if err != nil {
return nil, nil, "", "", err
}
comp := &common.ApplicationComponent{}
if err := compSettings.UnmarshalTo(comp); err != nil {
return nil, nil, "", "", err
}
patcher, err := v.LookupValue("patch")
if err != nil {
patcher = nil
}
clusterName, err := v.GetString("cluster")
if err != nil {
clusterName = ""
}
overrideNamespace, err := v.GetString("namespace")
if err != nil {
overrideNamespace = ""
}
return comp, patcher, clusterName, overrideNamespace, nil
}
// LoadComponent load component describe info in application.
func (p *provider) LoadComponent(ctx wfContext.Context, v *value.Value, act wfTypes.Action) error {
for _, comp := range p.app.Spec.Components {
@@ -114,13 +158,15 @@ func (p *provider) LoadComponent(ctx wfContext.Context, v *value.Value, act wfTy
}
// Install register handlers to provider discover.
func Install(p providers.Providers, app *v1beta1.Application, apply ComponentApply) {
func Install(p providers.Providers, app *v1beta1.Application, apply ComponentApply, render ComponentRender) {
prd := &provider{
apply: apply,
app: app.DeepCopy(),
render: render,
apply: apply,
app: app.DeepCopy(),
}
p.Register(ProviderName, map[string]providers.Handler{
"component-apply": prd.ApplyComponent,
"load": prd.LoadComponent,
"component-render": prd.RenderComponent,
"component-apply": prd.ApplyComponent,
"load": prd.LoadComponent,
})
}
+73 -18
View File
@@ -21,9 +21,10 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"github.com/stretchr/testify/require"
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
"gotest.tools/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
@@ -32,23 +33,23 @@ import (
)
func TestParser(t *testing.T) {
r := require.New(t)
p := &provider{
apply: simpleComponentApplyForTest,
}
act := &mock.Action{}
v, err := value.NewValue("", nil, "")
assert.NilError(t, err)
r.NoError(err)
err = p.ApplyComponent(nil, v, act)
assert.Error(t, err, "var(path=value) not exist")
r.Equal(err.Error(), "var(path=value) not exist")
v.FillObject(map[string]interface{}{}, "value")
err = p.ApplyComponent(nil, v, act)
assert.NilError(t, err)
r.NoError(err)
output, err := v.LookupValue("output")
assert.NilError(t, err)
r.NoError(err)
outStr, err := output.String()
assert.NilError(t, err)
assert.Equal(t, outStr, `apiVersion: "v1"
r.NoError(err)
r.Equal(outStr, `apiVersion: "v1"
kind: "Pod"
metadata: {
name: "rss-site"
@@ -59,10 +60,10 @@ metadata: {
`)
outputs, err := v.LookupValue("outputs")
assert.NilError(t, err)
r.NoError(err)
outsStr, err := outputs.String()
assert.NilError(t, err)
assert.Equal(t, outsStr, `service: {
r.NoError(err)
r.Equal(outsStr, `service: {
apiVersion: "v1"
kind: "Service"
metadata: {
@@ -74,15 +75,69 @@ metadata: {
}
`)
assert.Equal(t, act.Phase, "Wait")
r.Equal(act.Phase, "Wait")
testHealthy = true
act = &mock.Action{}
_, err = value.NewValue("", nil, "")
assert.NilError(t, err)
assert.Equal(t, act.Phase, "")
r.NoError(err)
r.Equal(act.Phase, "")
}
func TestRenderComponent(t *testing.T) {
r := require.New(t)
p := &provider{
render: func(comp common.ApplicationComponent, patcher *value.Value, clusterName string, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, error) {
return &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
},
}, []*unstructured.Unstructured{
{
Object: map[string]interface{}{
"apiVersion": "core.oam.dev/v1alpha2",
"kind": "ManualScalerTrait",
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"trait.oam.dev/resource": "scaler",
},
},
"spec": map[string]interface{}{"replicaCount": int64(10)},
},
},
}, nil
},
}
v, err := value.NewValue(`value: {}`, nil, "")
r.NoError(err)
err = p.RenderComponent(nil, v, nil)
r.NoError(err)
s, err := v.String()
r.NoError(err)
r.Equal(s, `value: {}
output: {
apiVersion: "apps/v1"
kind: "Deployment"
}
outputs: {
scaler: {
apiVersion: "core.oam.dev/v1alpha2"
kind: "ManualScalerTrait"
metadata: {
labels: {
"trait.oam.dev/resource": "scaler"
}
}
spec: {
replicaCount: 10
}
}
}
`)
}
func TestLoadComponent(t *testing.T) {
r := require.New(t)
p := &provider{
app: &v1beta1.Application{
Spec: v1beta1.ApplicationSpec{
@@ -97,12 +152,12 @@ func TestLoadComponent(t *testing.T) {
},
}
v, err := value.NewValue(``, nil, "")
assert.NilError(t, err)
r.NoError(err)
err = p.LoadComponent(nil, v, nil)
assert.NilError(t, err)
r.NoError(err)
s, err := v.String()
assert.NilError(t, err)
assert.Equal(t, s, `value: {
r.NoError(err)
r.Equal(s, `value: {
c1: {
name: *"c1" | _
type: *"web" | _