From d8d891f6c9d26413a833638dcc4fc4483703019e Mon Sep 17 00:00:00 2001 From: zzxwill Date: Wed, 30 Dec 2020 00:14:26 +0800 Subject: [PATCH 1/8] Allow trait to work without TraitDefinition Fix the issue of applying trait if its traitdefinition doesn't exits. To fix issue #839 --- ...figuration_without_traitdefinition_test.go | 156 ++++++++++++++++++ .../applicationconfiguration/apply.go | 6 +- .../applicationconfiguration/render.go | 7 +- .../autoscaler/autoscaler_controller.go | 18 +- pkg/oam/util/helper.go | 5 +- 5 files changed, 174 insertions(+), 18 deletions(-) create mode 100644 pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration_without_traitdefinition_test.go diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration_without_traitdefinition_test.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration_without_traitdefinition_test.go new file mode 100644 index 000000000..5f3765842 --- /dev/null +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration_without_traitdefinition_test.go @@ -0,0 +1,156 @@ +/* +Copyright 2020 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package applicationconfiguration + +import ( + "context" + "strconv" + "time" + + "github.com/ghodss/yaml" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2" + "github.com/oam-dev/kubevela/pkg/oam/util" +) + +var _ = Describe("Test Deploying ApplicationConfiguration without TraitDefinition", func() { + const ( + namespace = "definition-test" + appName = "hello" + componentName = "backend" + ) + var ( + ctx = context.Background() + workload v1alpha2.ContainerizedWorkload + component v1alpha2.Component + workloadKey = client.ObjectKey{ + Name: componentName, + Namespace: namespace, + } + appConfig v1alpha2.ApplicationConfiguration + appConfigKey = client.ObjectKey{ + Name: appName, + Namespace: namespace, + } + req = reconcile.Request{NamespacedName: appConfigKey} + ns = corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + } + ) + + BeforeEach(func() {}) + + It("ManualScalerTrait should work successfully even though its TraitDefinition doesn't exist", func() { + var componentStr = ` +apiVersion: core.oam.dev/v1alpha2 +kind: Component +metadata: + name: backend + namespace: definition-test +spec: + workload: + apiVersion: core.oam.dev/v1alpha2 + kind: ContainerizedWorkload + spec: + containers: + - name: nginx + image: nginx:1.9.4 + ports: + - containerPort: 80 + name: nginx + env: + - name: TEST_ENV + value: test + command: [ "/bin/bash", "-c", "--" ] + args: [ "while true; do sleep 30; done;" ] +` + + var appConfigStr = ` +apiVersion: core.oam.dev/v1alpha2 +kind: ApplicationConfiguration +metadata: + name: hello + namespace: definition-test +spec: + components: + - componentName: backend + traits: + - trait: + apiVersion: core.oam.dev/v1alpha2 + kind: ManualScalerTrait + spec: + replicaCount: 3 +` + + By("Create namespace") + Eventually( + func() error { + return k8sClient.Create(ctx, &ns) + }, + time.Second*3, time.Millisecond*300).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{})) + + By("Create Component") + Expect(yaml.Unmarshal([]byte(componentStr), &component)).Should(BeNil()) + Expect(k8sClient.Create(ctx, &component)).Should(Succeed()) + cmpV1 := &v1alpha2.Component{} + Expect(k8sClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: componentName}, cmpV1)).Should(Succeed()) + + By("Create ApplicationConfiguration") + Expect(yaml.Unmarshal([]byte(appConfigStr), &appConfig)).Should(BeNil()) + Expect(k8sClient.Create(ctx, &appConfig)).Should(Succeed()) + + By("Reconcile") + reconcileRetry(reconciler, req) + + By("Check workload created successfully") + Eventually(func() error { + return k8sClient.Get(ctx, workloadKey, &workload) + }, time.Second, 300*time.Millisecond).Should(BeNil()) + + By("Check reconcile again and no error will happen") + reconcileRetry(reconciler, req) + + By("Check appConfig condition should not have error") + Eventually(func() string { + By("Reconcile again and should not have error") + reconcileRetry(reconciler, req) + err := k8sClient.Get(ctx, appConfigKey, &appConfig) + if err != nil { + return err.Error() + } + if len(appConfig.Status.Conditions) != 1 { + return "condition len should be 1 but now is " + strconv.Itoa(len(appConfig.Status.Conditions)) + } + return string(appConfig.Status.Conditions[0].Reason) + }, 3*time.Second, 300*time.Millisecond).Should(BeEquivalentTo("ReconcileSuccess")) + }) + + AfterEach(func() { + // delete the namespace with all its resources + Expect(k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationForeground))). + Should(SatisfyAny(BeNil(), &util.NotFoundMatcher{})) + }) + +}) diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/apply.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/apply.go index 85e54703c..63323ac8c 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/apply.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/apply.go @@ -77,7 +77,7 @@ func (fn WorkloadApplyFns) Finalize(ctx context.Context, ac *v1alpha2.Applicatio type workloads struct { // use patching-apply for creating/updating Workload patchingClient resource.Applicator - // use updateing-apply for creating/updating Trait + // use updating-apply for creating/updating Trait updatingClient resource.Applicator rawClient client.Client dm discoverymapper.DiscoveryMapper @@ -91,7 +91,7 @@ func (a *workloads) Apply(ctx context.Context, status []v1alpha2.WorkloadStatus, if !wl.HasDep { err := a.patchingClient.Apply(ctx, wl.Workload, ao...) if err != nil { - // TODO(roywang) use errors.As() insteand of type assertion on error + // TODO(roywang) use errors.As() instead of type assertion on error if _, ok := err.(*GenerationUnchanged); !ok { // GenerationUnchanged only aborts applying current workload // but not blocks the whole reconciliation through returning an error @@ -105,7 +105,7 @@ func (a *workloads) Apply(ctx context.Context, status []v1alpha2.WorkloadStatus, } t := trait.Object if err := a.updatingClient.Apply(ctx, &trait.Object, ao...); err != nil { - // TODO(roywang) use errors.As() insteand of type assertion on error + // TODO(roywang) use errors.As() instead of type assertion on error if _, ok := err.(*GenerationUnchanged); !ok { // GenerationUnchanged only aborts applying current trait // but not blocks the whole reconciliation through returning an error diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/render.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/render.go index f664eb02c..53aec9627 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/render.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/render.go @@ -226,11 +226,10 @@ func (r *components) renderTrait(ctx context.Context, ct v1alpha2.ComponentTrait } traitDef, err := util.FetchTraitDefinition(ctx, r.client, r.dm, t) if err != nil { - if apierrors.IsNotFound(err) { - t.SetNamespace(ac.GetNamespace()) - return t, util.GetDummyTraitDefinition(t), nil + if !apierrors.IsNotFound(err) { + return nil, nil, errors.Wrapf(err, errFmtGetTraitDefinition, t.GetAPIVersion(), t.GetKind(), t.GetName()) } - return nil, nil, errors.Wrapf(err, errFmtGetTraitDefinition, t.GetAPIVersion(), t.GetKind(), t.GetName()) + traitDef = util.GetDummyTraitDefinition(t) } traitName := getTraitName(ac, componentName, &ct, t, traitDef) diff --git a/pkg/controller/standard.oam.dev/v1alpha1/autoscaler/autoscaler_controller.go b/pkg/controller/standard.oam.dev/v1alpha1/autoscaler/autoscaler_controller.go index 9957baf8f..61579a6c1 100644 --- a/pkg/controller/standard.oam.dev/v1alpha1/autoscaler/autoscaler_controller.go +++ b/pkg/controller/standard.oam.dev/v1alpha1/autoscaler/autoscaler_controller.go @@ -30,23 +30,21 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" + "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1" + "github.com/oam-dev/kubevela/pkg/controller/common" "github.com/oam-dev/kubevela/pkg/oam/discoverymapper" "github.com/oam-dev/kubevela/pkg/oam/util" oamutil "github.com/oam-dev/kubevela/pkg/oam/util" - - "github.com/oam-dev/kubevela/apis/standard.oam.dev/v1alpha1" - "github.com/oam-dev/kubevela/pkg/controller/common" ) // nolint:golint const ( - SpecWarningTargetWorkloadNotSet = "Spec.targetWorkload is not set" - SpecWarningStartAtTimeFormat = "startAt is not in the right format, which should be like `12:01`" - SpecWarningStartAtTimeRequired = "spec.triggers.condition.startAt: Required value" - SpecWarningDurationTimeRequired = "spec.triggers.condition.duration: Required value" - SpecWarningReplicasRequired = "spec.triggers.condition.replicas: Required value" - SpecWarningDurationTimeNotInRightFormat = "spec.triggers.condition.duration: not in the right format" - SpecWarningSumOfStartAndDurationMoreThan24Hour = "the sum of the start hour and the duration hour has to be less than 24 hours." + SpecWarningTargetWorkloadNotSet = "Spec.targetWorkload is not set" + SpecWarningStartAtTimeFormat = "startAt is not in the right format, which should be like `12:01`" + SpecWarningStartAtTimeRequired = "spec.triggers.condition.startAt: Required value" + SpecWarningDurationTimeRequired = "spec.triggers.condition.duration: Required value" + SpecWarningReplicasRequired = "spec.triggers.condition.replicas: Required value" + SpecWarningDurationTimeNotInRightFormat = "spec.triggers.condition.duration: not in the right format" ) // ReconcileWaitResult is the time to wait between reconciliation. diff --git a/pkg/oam/util/helper.go b/pkg/oam/util/helper.go index ffb3de8a7..d352c849a 100644 --- a/pkg/oam/util/helper.go +++ b/pkg/oam/util/helper.go @@ -135,7 +135,10 @@ func GetDummyTraitDefinition(u *unstructured.Unstructured) *v1alpha2.TraitDefini "kind": u.GetKind(), "name": u.GetName(), }}, - Spec: v1alpha2.TraitDefinitionSpec{Reference: v1alpha2.DefinitionReference{Name: Dummy}}, + Spec: v1alpha2.TraitDefinitionSpec{ + Reference: v1alpha2.DefinitionReference{Name: Dummy}, + WorkloadRefPath: "spec.workloadRef", + }, } } From 0bd85d359a3ce131848810853cbedc8f795a976f Mon Sep 17 00:00:00 2001 From: zzxwill Date: Wed, 30 Dec 2020 11:04:18 +0800 Subject: [PATCH 2/8] Refine Dashboard README Refined dashboard readme with all-in-one start command --- dashboard/README.md | 73 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/dashboard/README.md b/dashboard/README.md index e8f207808..b47c064f1 100644 --- a/dashboard/README.md +++ b/dashboard/README.md @@ -1,31 +1,68 @@ -# Vela Dashboard +# KubeVela Dashboard + +## Quick start + +In the root folder of this project, run `make start-dashboard` to start backend OpenAPI server and Dashboard at the same time. + +```shell +➜ xxx/src/github.com/oam-dev/kubevela $ make start-dashboard +go run pkg/server/main/startAPIServer.go & +cd dashboard && yarn && yarn start && cd .. +yarn install v1.22.4 +warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json. +[1/5] 🔍 Validating package.json... +[2/5] 🔍 Resolving packages... +success Already up-to-date. +$ umi g tmp +✨ Done in 5.89s. +yarn run v1.22.4 +$ umi dev +Starting the development server... +I1230 10:37:54.157092 14236 request.go:621] Throttling request took 1.04915427s, request: GET:https://47.242.145.141:6443/apis/split.smi-spec.io/v1alpha2?timeout=32s +[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. + - using env: export GIN_MODE=release + - using code: gin.SetMode(gin.ReleaseMode) + +[GIN-debug] POST /api/envs/ --> github.com/oam-dev/kubevela/pkg/server.(*APIServer).CreateEnv-fm (6 handlers) +[GIN-debug] PUT /api/envs/:envName --> github.com/oam-dev/kubevela/pkg/server.(*APIServer).UpdateEnv-fm (6 handlers) +... +[GIN-debug] GET /api/version --> github.com/oam-dev/kubevela/pkg/server.(*APIServer).GetVersion-fm (6 handlers) +[GIN-debug] GET /swagger/*any --> github.com/swaggo/gin-swagger.CustomWrapHandler.func1 (7 handlers) + +✔ Webpack + Compiled successfully in 26.86s + + DONE Compiled successfully in 26865ms 10:38:22 AM -## Environment Prepare + App running at: + - Local: http://localhost:8000 (copied to clipboard) + - Network: http://192.168.31.114:8000 +``` -Install `node_modules`: +## Development + +### Install dependencies ```bash yarn ``` -## Provided Scripts - -Scripts provided in `package.json`. It's safe to modify or add additional script: - -### Start project - -```bash -yarn start -``` - -### Build project +### Build ```bash yarn build ``` -### Check code style +### Start up + +```bash +yarn start +``` + +### Lint and Test + +- Check code style ```bash yarn lint @@ -34,11 +71,11 @@ yarn lint You can also use script to auto fix some lint error: ```bash -yarn lint:fix +yarn prettier ``` -### Test code +- Test code ```bash yarn test -``` \ No newline at end of file +``` From 02bb9a1dc1a8d5aa65d09a5ef33b7e2285f3383e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E5=85=83?= Date: Wed, 30 Dec 2020 14:19:45 +0800 Subject: [PATCH 3/8] refine error message: trait definition not found --- .../application/application_controller.go | 2 +- .../v1alpha2/application/parser/service.go | 18 +++-- .../application/parser/service_test.go | 2 +- .../v1alpha2/application/template/template.go | 65 +++++++++---------- .../application/template/template_test.go | 8 +-- .../application/validating_handler.go | 2 +- 6 files changed, 44 insertions(+), 53 deletions(-) diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go index cd3f1be3b..a9eea06bb 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/application_controller.go @@ -74,7 +74,7 @@ func (r *Reconciler) Reconcile(req ctrl.Request) (result ctrl.Result, gerr error applog.Info("parse template") // parse template - appParser := parser.NewParser(template.GetHanler(fclient.NewDefinitionClient(r.Client))) + appParser := parser.NewParser(template.GetHandler(fclient.NewDefinitionClient(r.Client))) appfile, err := appParser.Parse(app.Name, app) diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/parser/service.go b/pkg/controller/core.oam.dev/v1alpha2/application/parser/service.go index 088bf36ad..3e107ed05 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/parser/service.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/parser/service.go @@ -8,6 +8,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2" + "github.com/oam-dev/kubevela/apis/types" "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/template" "github.com/oam-dev/kubevela/pkg/dsl/definition" "github.com/oam-dev/kubevela/pkg/dsl/process" @@ -183,13 +184,10 @@ func (pser *Parser) parseWorkload(comp v1alpha2.ApplicationComponent) (*Workload workload.traits = []*Trait{} workload.name = comp.Name workload.typ = comp.WorkloadType - templ, kind, err := pser.templ(workload.typ) + templ, err := pser.templ(workload.typ, types.TypeWorkload) if err != nil && !kerrors.IsNotFound(err) { return nil, errors.WithMessagef(err, "fetch type of %s", comp.Name) } - if kind != template.WorkloadKind { - return nil, errors.Errorf("%s type (%s) invalid", comp.Name, workload.typ) - } workload.template = templ settings, err := DecodeJSONMarshaler(comp.Settings) if err != nil { @@ -214,14 +212,14 @@ func (pser *Parser) parseWorkload(comp v1alpha2.ApplicationComponent) (*Workload } func (pser *Parser) parseTrait(name string, properties map[string]interface{}) (*Trait, error) { - - templ, kind, err := pser.templ(name) - if err != nil && !kerrors.IsNotFound(err) { + templ, err := pser.templ(name, types.TypeTrait) + if kerrors.IsNotFound(err) { + return nil, errors.Errorf("trait definition of %s not found", name) + } + if err != nil { return nil, err } - if kind != template.TraitKind { - return nil, errors.Errorf("kind of %s is not trait", name) - } + trait := new(Trait) trait.template = templ trait.name = name diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/parser/service_test.go b/pkg/controller/core.oam.dev/v1alpha2/application/parser/service_test.go index 672ff370d..c06bcb59c 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/parser/service_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/parser/service_test.go @@ -116,7 +116,7 @@ spec: o := v1alpha2.Application{} yaml.Unmarshal([]byte(appfileYaml), &o) - appfile, err := NewParser(template.GetHanler(mock)).Parse("test", &o) + appfile, err := NewParser(template.GetHandler(mock)).Parse("test", &o) if err != nil { t.Error(err) return diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/template/template.go b/pkg/controller/core.oam.dev/v1alpha2/application/template/template.go index ee82a85b9..73d3c3b43 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/template/template.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/template/template.go @@ -4,6 +4,8 @@ import ( "encoding/json" "fmt" + "github.com/oam-dev/kubevela/apis/types" + "github.com/pkg/errors" kerrors "k8s.io/apimachinery/pkg/api/errors" @@ -14,8 +16,8 @@ type manager struct { defclient.DefinitionClient } -// GetHanler get template handler -func GetHanler(cli defclient.DefinitionClient) Handler { +// GetHandler get template handler +func GetHandler(cli defclient.DefinitionClient) Handler { m := &manager{ DefinitionClient: cli, } @@ -23,51 +25,46 @@ func GetHanler(cli defclient.DefinitionClient) Handler { } // Handler is template handler type -type Handler func(key string) (string, Kind, error) +type Handler func(key string, kind types.CapType) (string, error) // Kind is template kind -type Kind uint16 - -const ( - // WorkloadKind ... - WorkloadKind Kind = (1 << iota) - // TraitKind ... - TraitKind - // Unkownkind ... - Unkownkind -) +type Kind = types.CapType // LoadTemplate Get template according to key -func (m *manager) LoadTemplate(key string) (string, Kind, error) { - wd, err := m.GetWorkloadDefinition(key) - if err != nil && !kerrors.IsNotFound(err) { - return "", Unkownkind, errors.WithMessagef(err, "LoadTemplate [%s] ", key) - } - if wd != nil { +func (m *manager) LoadTemplate(key string, kd types.CapType) (string, error) { + switch kd { + case types.TypeWorkload: + wd, err := m.GetWorkloadDefinition(key) + if err != nil { + return "", errors.WithMessagef(err, "LoadTemplate [%s] ", key) + } jsonRaw, err := getTemplate(wd.Spec.Extension.Raw) if err != nil { - return "", Unkownkind, errors.WithMessagef(err, "LoadTemplate [%s] ", key) + return "", errors.WithMessagef(err, "LoadTemplate [%s] ", key) } - if jsonRaw != "" { - return jsonRaw, WorkloadKind, nil + if jsonRaw == "" { + return "", errors.New("no template found in definition") + } + return jsonRaw, nil + + case types.TypeTrait: + td, err := m.GetTraitDefition(key) + if err != nil && !kerrors.IsNotFound(err) { + return "", errors.WithMessagef(err, "LoadTemplate [%s] ", key) } - } - td, err := m.GetTraitDefition(key) - if err != nil && !kerrors.IsNotFound(err) { - return "", Unkownkind, errors.WithMessagef(err, "LoadTemplate [%s] ", key) - } - if td != nil { jsonRaw, err := getTemplate(td.Spec.Extension.Raw) if err != nil { - return "", Unkownkind, errors.WithMessagef(err, "LoadTemplate [%s] ", key) + return "", errors.WithMessagef(err, "LoadTemplate [%s] ", key) } - - if jsonRaw != "" { - return jsonRaw, TraitKind, nil + if jsonRaw == "" { + return "", errors.New("no template found in definition") } - + return jsonRaw, nil + case types.TypeScope: + // TODO: add scope template support } - return "", Unkownkind, nil + + return "", fmt.Errorf("kind(%s) of %s not supported", kd, key) } func getTemplate(raw []byte) (string, error) { diff --git a/pkg/controller/core.oam.dev/v1alpha2/application/template/template_test.go b/pkg/controller/core.oam.dev/v1alpha2/application/template/template_test.go index a9b654e5f..d8d4c7e68 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/application/template/template_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/application/template/template_test.go @@ -5,6 +5,7 @@ import ( "cuelang.org/go/cue" + "github.com/oam-dev/kubevela/apis/types" "github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application/defclient" ) @@ -78,16 +79,11 @@ spec: m := manager{ mock, } - temp, kind, err := m.LoadTemplate("worker") + temp, err := m.LoadTemplate("worker", types.TypeWorkload) if err != nil { t.Error(err) return } - if kind != WorkloadKind { - t.Errorf("template.LoadTemplate kind invalid") - return - } - var r cue.Runtime inst, err := r.Compile("-", temp) if err != nil { diff --git a/pkg/webhook/core.oam.dev/v1alpha2/application/validating_handler.go b/pkg/webhook/core.oam.dev/v1alpha2/application/validating_handler.go index 8dab58fa5..cfea8b925 100644 --- a/pkg/webhook/core.oam.dev/v1alpha2/application/validating_handler.go +++ b/pkg/webhook/core.oam.dev/v1alpha2/application/validating_handler.go @@ -59,7 +59,7 @@ func (h *ValidatingHandler) Handle(ctx context.Context, req admission.Request) a } // try render to validate - appParser := parser.NewParser(template.GetHanler(fclient.NewDefinitionClient(h.Client))) + appParser := parser.NewParser(template.GetHandler(fclient.NewDefinitionClient(h.Client))) if _, err := appParser.Parse(app.Name, app); err != nil { return admission.Denied(err.Error()) } From dfdb833abef2ac995522b4210914bcfefb901bf7 Mon Sep 17 00:00:00 2001 From: zzxwill Date: Wed, 30 Dec 2020 14:45:29 +0800 Subject: [PATCH 4/8] Check trait CR is created and update ApplicationConfiguration --- ...figuration_without_traitdefinition_test.go | 80 ++++++++++++++++++- pkg/oam/util/helper.go | 5 +- 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration_without_traitdefinition_test.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration_without_traitdefinition_test.go index 5f3765842..f3e8758c0 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration_without_traitdefinition_test.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration_without_traitdefinition_test.go @@ -18,9 +18,15 @@ package applicationconfiguration import ( "context" + "fmt" "strconv" + "strings" "time" + "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1" + + "k8s.io/apimachinery/pkg/runtime" + "github.com/ghodss/yaml" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -101,7 +107,11 @@ spec: apiVersion: core.oam.dev/v1alpha2 kind: ManualScalerTrait spec: - replicaCount: 3 + replicaCount: 2 + workloadRef: + apiVersion: core.oam.dev/v1alpha2 + kind: ContainerizedWorkload + name: backend ` By("Create namespace") @@ -123,6 +133,7 @@ spec: By("Reconcile") reconcileRetry(reconciler, req) + time.Sleep(5) By("Check workload created successfully") Eventually(func() error { @@ -145,6 +156,72 @@ spec: } return string(appConfig.Status.Conditions[0].Reason) }, 3*time.Second, 300*time.Millisecond).Should(BeEquivalentTo("ReconcileSuccess")) + + By("Check trait CR is created") + var scaleName string + scaleList := v1alpha2.ManualScalerTraitList{} + labels := &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app.oam.dev/component": componentName, + }, + } + selector, _ := metav1.LabelSelectorAsSelector(labels) + err := k8sClient.List(ctx, &scaleList, &client.ListOptions{ + Namespace: namespace, + LabelSelector: selector, + }) + Expect(err).Should(BeNil()) + traitNamePrefix := fmt.Sprintf("%s-dummy-", componentName) + var traitExistFlag bool + for _, t := range scaleList.Items { + if strings.HasPrefix(t.Name, traitNamePrefix) { + traitExistFlag = true + scaleName = t.Name + } + } + Expect(traitExistFlag).Should(BeTrue()) + + By("Update ApplicationConfiguration by changing spec of trait") + newTrait := &v1alpha2.ManualScalerTrait{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "core.oam.dev/v1alpha2", + Kind: "ManualScalerTrait", + }, + Spec: v1alpha2.ManualScalerTraitSpec{ + ReplicaCount: 3, + WorkloadReference: v1alpha1.TypedReference{ + APIVersion: "core.oam.dev/v1alpha2", + Kind: "ContainerizedWorkload", + Name: componentName, + }, + }, + } + appConfig.Spec.Components[0].Traits = []v1alpha2.ComponentTrait{{Trait: runtime.RawExtension{Object: newTrait.DeepCopyObject()}}} + Expect(k8sClient.Update(ctx, &appConfig)).Should(BeNil()) + + By("Reconcile") + reconcileRetry(reconciler, req) + + By("Check again that appConfig condition should not have error") + Eventually(func() string { + By("Reconcile again and should not have error") + reconcileRetry(reconciler, req) + err := k8sClient.Get(ctx, appConfigKey, &appConfig) + if err != nil { + return err.Error() + } + if len(appConfig.Status.Conditions) != 1 { + return "condition len should be 1 but now is " + strconv.Itoa(len(appConfig.Status.Conditions)) + } + return string(appConfig.Status.Conditions[0].Reason) + }, 3*time.Second, 300*time.Millisecond).Should(BeEquivalentTo("ReconcileSuccess")) + + By("Check new trait CR is applied") + scale := v1alpha2.ManualScalerTrait{} + scaleKey := client.ObjectKey{Name: scaleName, Namespace: namespace} + err = k8sClient.Get(ctx, scaleKey, &scale) + Expect(err).Should(BeNil()) + Expect(scale.Spec.ReplicaCount).Should(Equal(int32(3))) }) AfterEach(func() { @@ -152,5 +229,4 @@ spec: Expect(k8sClient.Delete(ctx, &ns, client.PropagationPolicy(metav1.DeletePropagationForeground))). Should(SatisfyAny(BeNil(), &util.NotFoundMatcher{})) }) - }) diff --git a/pkg/oam/util/helper.go b/pkg/oam/util/helper.go index d352c849a..ffb3de8a7 100644 --- a/pkg/oam/util/helper.go +++ b/pkg/oam/util/helper.go @@ -135,10 +135,7 @@ func GetDummyTraitDefinition(u *unstructured.Unstructured) *v1alpha2.TraitDefini "kind": u.GetKind(), "name": u.GetName(), }}, - Spec: v1alpha2.TraitDefinitionSpec{ - Reference: v1alpha2.DefinitionReference{Name: Dummy}, - WorkloadRefPath: "spec.workloadRef", - }, + Spec: v1alpha2.TraitDefinitionSpec{Reference: v1alpha2.DefinitionReference{Name: Dummy}}, } } From 01bd05d0a51a74133ff3c3c52acbf7e3dcc217f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A9=E5=85=83?= Date: Thu, 31 Dec 2020 16:41:55 +0800 Subject: [PATCH 5/8] add test for component revision hook --- cmd/core/main.go | 2 + .../core.oam.dev/oamruntime_controller.go | 4 + .../applicationconfiguration.go | 7 +- .../applicationconfiguration/component.go | 10 +-- .../component_custom_revision.go | 8 +- .../component_custom_revision_test.go | 77 +++++++++++++++++++ .../healthscope/healthscope_controller.go | 2 +- .../manualscalertrait_controller.go | 2 +- .../containerizedworkload_controller.go | 2 +- 9 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component_custom_revision_test.go diff --git a/cmd/core/main.go b/cmd/core/main.go index 5623d4ebc..18ce1642c 100644 --- a/cmd/core/main.go +++ b/cmd/core/main.go @@ -94,6 +94,8 @@ func main() { flag.StringVar(&healthAddr, "health-addr", ":9440", "The address the health endpoint binds to.") flag.BoolVar(&controllerArgs.ApplyOnceOnly, "apply-once-only", false, "For the purpose of some production environment that workload or trait should not be affected if no spec change") + flag.StringVar(&controllerArgs.CustomRevisionHookURL, "custom-revision-hook-url", "", + "custom-revision-hook-url is a webhook url which will let oam-runtime to call with AC+Component info and return a customized component revision") flag.StringVar(&disableCaps, "disable-caps", "", "To be disabled builtin capability list.") flag.Parse() diff --git a/pkg/controller/core.oam.dev/oamruntime_controller.go b/pkg/controller/core.oam.dev/oamruntime_controller.go index 06e5c6a2c..b5b81f275 100644 --- a/pkg/controller/core.oam.dev/oamruntime_controller.go +++ b/pkg/controller/core.oam.dev/oamruntime_controller.go @@ -25,4 +25,8 @@ type Args struct { // ApplyOnceOnly indicates whether workloads and traits should be // affected if no spec change is made in the ApplicationConfiguration. ApplyOnceOnly bool + + // CustomRevisionHookURL is a webhook which will let oam-runtime to call with AC+Component info + // The webhook server will return a customized component revision for oam-runtime + CustomRevisionHookURL string } diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go index 2477442c2..760bfe85b 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/applicationconfiguration.go @@ -94,9 +94,10 @@ func Setup(mgr ctrl.Manager, args core.Args, l logging.Logger) error { Named(name). For(&v1alpha2.ApplicationConfiguration{}). Watches(&source.Kind{Type: &v1alpha2.Component{}}, &ComponentHandler{ - Client: mgr.GetClient(), - Logger: l, - RevisionLimit: args.RevisionLimit, + Client: mgr.GetClient(), + Logger: l, + RevisionLimit: args.RevisionLimit, + CustomRevisionHookURL: args.CustomRevisionHookURL, }). Complete(NewReconciler(mgr, dm, WithLogger(l.WithValues("controller", name)), diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component.go index 3da58c466..a9a086def 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component.go @@ -29,10 +29,10 @@ const ControllerRevisionComponentLabel = "controller.oam.dev/component" // ComponentHandler will watch component change and generate Revision automatically. type ComponentHandler struct { - Client client.Client - Logger logging.Logger - RevisionLimit int - CustomWebHookURL string + Client client.Client + Logger logging.Logger + RevisionLimit int + CustomRevisionHookURL string } // Create implements EventHandler @@ -148,7 +148,7 @@ func (c *ComponentHandler) createControllerRevision(mt metav1.Object, obj runtim reqs := c.getRelatedAppConfig(mt) // Hook to custom revision service if exist if err := c.customComponentRevisionHook(reqs, comp); err != nil { - c.Logger.Info(fmt.Sprintf("fail to hook from custom revision service(%s) %v", c.CustomWebHookURL, err), "componentName", mt.GetName()) + c.Logger.Info(fmt.Sprintf("fail to hook from custom revision service(%s) %v", c.CustomRevisionHookURL, err), "componentName", mt.GetName()) return nil, false } diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component_custom_revision.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component_custom_revision.go index 3765578b2..60350d0c6 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component_custom_revision.go +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component_custom_revision.go @@ -20,6 +20,7 @@ import ( "bytes" "context" "encoding/json" + "fmt" "io/ioutil" "net/http" @@ -36,7 +37,7 @@ type RevisionHookRequest struct { } func (c *ComponentHandler) customComponentRevisionHook(relatedApps []reconcile.Request, comp *v1alpha2.Component) error { - if c.CustomWebHookURL == "" { + if c.CustomRevisionHookURL == "" { return nil } req := RevisionHookRequest{ @@ -47,7 +48,7 @@ func (c *ComponentHandler) customComponentRevisionHook(relatedApps []reconcile.R if err != nil { return err } - httpRequest, err := http.NewRequestWithContext(context.Background(), http.MethodPost, c.CustomWebHookURL, bytes.NewBuffer(data)) + httpRequest, err := http.NewRequestWithContext(context.Background(), http.MethodPost, c.CustomRevisionHookURL, bytes.NewBuffer(data)) if err != nil { return err } @@ -62,5 +63,8 @@ func (c *ComponentHandler) customComponentRevisionHook(relatedApps []reconcile.R if err != nil { return err } + if resp.StatusCode != 200 { + return fmt.Errorf("httpcode(%d) err: %s", resp.StatusCode, string(respData)) + } return json.Unmarshal(respData, comp) } diff --git a/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component_custom_revision_test.go b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component_custom_revision_test.go new file mode 100644 index 000000000..c2c6f5bd6 --- /dev/null +++ b/pkg/controller/core.oam.dev/v1alpha2/applicationconfiguration/component_custom_revision_test.go @@ -0,0 +1,77 @@ +/* +Copyright 2020 The KubeVela Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package applicationconfiguration + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha2" +) + +func TestCustomRevisionHook(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var req RevisionHookRequest + data, err := ioutil.ReadAll(r.Body) + if err != nil { + w.WriteHeader(400) + return + } + err = json.Unmarshal(data, &req) + if err != nil { + w.WriteHeader(401) + return + } + if len(req.RelatedApps) != 1 { + w.WriteHeader(400) + w.Write([]byte("we should have only one relatedApps")) + return + } + if req.Comp.Annotations == nil { + req.Comp.Annotations = make(map[string]string) + } + req.Comp.Annotations["app-name"] = req.RelatedApps[0].Name + req.Comp.Annotations["app-namespace"] = req.RelatedApps[0].Namespace + + newdata, err := json.Marshal(req.Comp) + if err != nil { + w.WriteHeader(500) + return + } + w.WriteHeader(200) + w.Write(newdata) + })) + defer srv.Close() + compHandler := ComponentHandler{ + CustomRevisionHookURL: srv.URL, + } + comp := &v1alpha2.Component{} + err := compHandler.customComponentRevisionHook([]reconcile.Request{{NamespacedName: types.NamespacedName{Name: "app1", Namespace: "default1"}}}, comp) + assert.NoError(t, err) + assert.Equal(t, "app1", comp.Annotations["app-name"]) + assert.Equal(t, "default1", comp.Annotations["app-namespace"]) + + err = compHandler.customComponentRevisionHook([]reconcile.Request{{NamespacedName: types.NamespacedName{Name: "app1", Namespace: "default1"}}, {NamespacedName: types.NamespacedName{Name: "app2", Namespace: "default2"}}}, comp) + assert.Equal(t, err.Error(), "httpcode(400) err: we should have only one relatedApps") +} diff --git a/pkg/controller/core.oam.dev/v1alpha2/core/scopes/healthscope/healthscope_controller.go b/pkg/controller/core.oam.dev/v1alpha2/core/scopes/healthscope/healthscope_controller.go index b34a47573..a63efe935 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/core/scopes/healthscope/healthscope_controller.go +++ b/pkg/controller/core.oam.dev/v1alpha2/core/scopes/healthscope/healthscope_controller.go @@ -54,7 +54,7 @@ const ( ) // Setup adds a controller that reconciles HealthScope. -func Setup(mgr ctrl.Manager, args controller.Args, l logging.Logger) error { +func Setup(mgr ctrl.Manager, _ controller.Args, l logging.Logger) error { name := "oam/" + strings.ToLower(v1alpha2.HealthScopeGroupKind) return ctrl.NewControllerManagedBy(mgr). diff --git a/pkg/controller/core.oam.dev/v1alpha2/core/traits/manualscalertrait/manualscalertrait_controller.go b/pkg/controller/core.oam.dev/v1alpha2/core/traits/manualscalertrait/manualscalertrait_controller.go index ab3bb2fc0..1649a180f 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/core/traits/manualscalertrait/manualscalertrait_controller.go +++ b/pkg/controller/core.oam.dev/v1alpha2/core/traits/manualscalertrait/manualscalertrait_controller.go @@ -51,7 +51,7 @@ const ( ) // Setup adds a controller that reconciles ContainerizedWorkload. -func Setup(mgr ctrl.Manager, args controller.Args, log logging.Logger) error { +func Setup(mgr ctrl.Manager, _ controller.Args, _ logging.Logger) error { dm, err := discoverymapper.New(mgr.GetConfig()) if err != nil { return err diff --git a/pkg/controller/core.oam.dev/v1alpha2/core/workloads/containerizedworkload/containerizedworkload_controller.go b/pkg/controller/core.oam.dev/v1alpha2/core/workloads/containerizedworkload/containerizedworkload_controller.go index d15d46dbf..e83a484f1 100644 --- a/pkg/controller/core.oam.dev/v1alpha2/core/workloads/containerizedworkload/containerizedworkload_controller.go +++ b/pkg/controller/core.oam.dev/v1alpha2/core/workloads/containerizedworkload/containerizedworkload_controller.go @@ -49,7 +49,7 @@ const ( ) // Setup adds a controller that reconciles ContainerizedWorkload. -func Setup(mgr ctrl.Manager, args controller.Args, log logging.Logger) error { +func Setup(mgr ctrl.Manager, _ controller.Args, _ logging.Logger) error { reconciler := Reconciler{ Client: mgr.GetClient(), log: ctrl.Log.WithName("ContainerizedWorkload"), From 8d0595ed4e2d4145b1e8f857bd36ead5b3028ac7 Mon Sep 17 00:00:00 2001 From: Jianbo Sun Date: Thu, 31 Dec 2020 17:13:13 +0800 Subject: [PATCH 6/8] Update cmd/core/main.go --- cmd/core/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/core/main.go b/cmd/core/main.go index 18ce1642c..21489eaae 100644 --- a/cmd/core/main.go +++ b/cmd/core/main.go @@ -95,7 +95,7 @@ func main() { flag.BoolVar(&controllerArgs.ApplyOnceOnly, "apply-once-only", false, "For the purpose of some production environment that workload or trait should not be affected if no spec change") flag.StringVar(&controllerArgs.CustomRevisionHookURL, "custom-revision-hook-url", "", - "custom-revision-hook-url is a webhook url which will let oam-runtime to call with AC+Component info and return a customized component revision") + "custom-revision-hook-url is a webhook url which will let KubeVela core to call with applicationConfiguration and component info and return a customized component revision") flag.StringVar(&disableCaps, "disable-caps", "", "To be disabled builtin capability list.") flag.Parse() From 72ee5db872f9655dee980f8638745334f6b8819a Mon Sep 17 00:00:00 2001 From: guoxudong Date: Tue, 5 Jan 2021 05:30:59 +0800 Subject: [PATCH 7/8] Fix `each child in a list should have a unique "key" prop.' (#844) * fix capability * fix --- .../Components/ShowComponent/index.tsx | 20 ++++++------- .../src/pages/Capability/Traits/index.tsx | 17 ++++------- .../src/pages/Capability/Workloads/index.tsx | 11 +------- dashboard/yarn.lock | 28 +++++++++++++++++++ 4 files changed, 44 insertions(+), 32 deletions(-) diff --git a/dashboard/src/pages/Capability/Components/ShowComponent/index.tsx b/dashboard/src/pages/Capability/Components/ShowComponent/index.tsx index bbc7b8bb8..c1d9c5c1b 100644 --- a/dashboard/src/pages/Capability/Components/ShowComponent/index.tsx +++ b/dashboard/src/pages/Capability/Components/ShowComponent/index.tsx @@ -31,14 +31,14 @@ export default ({ name, parameters }: ShowParameters) => { dataIndex: 'name', key: 'name', width: 200, - render: (text, row) => [ + render: (text, row) => ( {row.name} {!row.required ? undefined : request} - , - ], + + ), }, { title: 'Short', @@ -46,11 +46,11 @@ export default ({ name, parameters }: ShowParameters) => { key: 'short', width: 100, responsive: ['md'], - render: (text, row) => [ + render: (text, row) => ( {!row.short ? undefined : {text}} - , - ], + + ), }, { title: 'Usage', @@ -71,11 +71,11 @@ export default ({ name, parameters }: ShowParameters) => { key: 'default', width: 200, responsive: ['md'], - render: (text, row) => [ + render: (text, row) => ( - {!row.default ? undefined : {text}} - , - ], + {!row.default ? undefined : {text.toString()}} + + ), }, ]} dataSource={parameters} diff --git a/dashboard/src/pages/Capability/Traits/index.tsx b/dashboard/src/pages/Capability/Traits/index.tsx index 014634552..eafcbdf0e 100644 --- a/dashboard/src/pages/Capability/Traits/index.tsx +++ b/dashboard/src/pages/Capability/Traits/index.tsx @@ -27,13 +27,13 @@ export default (): React.ReactNode => { - rowKey={(record) => record.name} + rowKey="name" headerTitle="Type" pagination={{ defaultPageSize: 5, showSizeChanger: false, }} - loading={loading ? { delay: 300 } : undefined} + loading={loading ? { delay: 60 } : undefined} dataSource={traitsList ?? []} split metas={{ @@ -49,22 +49,15 @@ export default (): React.ReactNode => { ); }, }, - subTitle: { - render: (text, row) => { - return ( - - {row.crdName} - - ); - }, - }, content: { render: (text, row) => { return ( applies to:  {row.appliesTo.map((item: string) => ( - {item} + + {item} + ))} ); diff --git a/dashboard/src/pages/Capability/Workloads/index.tsx b/dashboard/src/pages/Capability/Workloads/index.tsx index 13d5dc0df..5b1962570 100644 --- a/dashboard/src/pages/Capability/Workloads/index.tsx +++ b/dashboard/src/pages/Capability/Workloads/index.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; -import { Button, Card, Space, Tag, Typography } from 'antd'; +import { Button, Card, Typography } from 'antd'; import { useModel } from 'umi'; import { PageContainer } from '@ant-design/pro-layout'; import { FileWordTwoTone, SnippetsTwoTone } from '@ant-design/icons'; @@ -50,15 +50,6 @@ export default (): React.ReactNode => { ); }, }, - subTitle: { - render: (text, row) => { - return ( - - {!row.required ? undefined : Required} - - ); - }, - }, actions: { render: (text, row) => [