mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-18 20:17:04 +00:00
* feat(workflow): add deploy-components step for per-component multi-cluster targeting Adds a pure-CUE workflow-step definition that lets each component in an Application resolve its own target cluster(s) via topology policies, filling the gap between apply-component (single component, static cluster) and deploy (all components, cross-product against policies). Signed-off-by: vishal210893 <vishal210893@gmail.com> * chore(workflow): add rendered deploy-components WorkflowStepDefinition manifest Generated from vela-templates/definitions/internal/workflowstep/deploy-components.cue via make def-install, for installation as part of the vela-core Helm chart. Signed-off-by: vishal210893 <vishal210893@gmail.com> * test(e2e): add multicluster e2e for deploy-components step Covers per-component topology-policy targeting (each component lands only on its resolved cluster, no cross-product) and fast-fail validation when a referenced component name is not present in the application. Signed-off-by: vishal210893 <vishal210893@gmail.com> * docs(workflow): add example doc for deploy-components step Required by the definition-doc CI check for any new WorkflowStepDefinition. Signed-off-by: vishal210893 <vishal210893@gmail.com> * fix(workflow): normalize deploy-components import block to grouped form The rendered manifest was generated with flat "import X" lines; the project's CUE formatter (as used by CI's make reviewable) renders multi-import blocks grouped as import (...). Fixes the check-diff CI job. Signed-off-by: vishal210893 <vishal210893@gmail.com> * fix(workflow): gate deploy-components applies on validation to prevent partial rollout Previously, _missingComponents was derived from deploy's own realized keys, which meant an invalid component name could only be detected after applies for valid components had already started -- a partial rollout on failure. Compute _missingComponents directly from the loaded component set instead (via iteration, matching the pattern deploy itself already uses reliably -- an indexed lookup against the same field was empirically found to evaluate before the load task resolves, causing false positives), and wrap deploy in an if-guard so no component is applied unless every referenced name is valid. Verified against a live multicluster setup: the negative case now applies zero resources before failing, and the positive case is unaffected. Signed-off-by: vishal210893 <vishal210893@gmail.com> * style(workflow): tighten deploy-components comments Comments narrated the debugging history behind each constraint; state them as plain engineering facts instead. Signed-off-by: vishal210893 <vishal210893@gmail.com> * fix(workflow): correct mistyped #do string in multicluster.#GetPlacementsFromTopologyPolicies The exported CUE symbol's #do value ("get-placements-from-tmulticlusterology-policies") never matched the registered provider handler key ("get-placements-from-topology-policies"), making it unusable by any CUE template. Fix the string and rename the symbol to match. Unused elsewhere in the tree, so no other callers are affected. deploy-components keeps its own local copy (for backport safety) but now documents that it mirrors the corrected symbol. Signed-off-by: vishal210893 <vishal210893@gmail.com> * refactor(workflow): use the shared topology-placement symbol in deploy-components Now that multicluster.#GetPlacementsFromTopologyPolicies is fixed, drop the local #GetPlacements duplicate and reference the shared symbol directly. deploy-components is new in this release, so there's no backport scenario where it could ship without the fix landing alongside it. Verified against a live multicluster controller build: positive case resolves placements and applies correctly, negative case still gates deploy on validation with zero partial rollout. Signed-off-by: vishal210893 <vishal210893@gmail.com> --------- Signed-off-by: vishal210893 <vishal210893@gmail.com>
108 lines
4.7 KiB
Go
108 lines
4.7 KiB
Go
/*
|
|
Copyright 2021 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 e2e_multicluster_test
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"time"
|
|
|
|
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
corev1 "k8s.io/api/core/v1"
|
|
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/yaml"
|
|
|
|
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
|
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
|
)
|
|
|
|
var _ = Describe("Test deploy-components workflow step", func() {
|
|
|
|
var namespace string
|
|
var hubCtx context.Context
|
|
var workerCtx context.Context
|
|
|
|
BeforeEach(func() {
|
|
hubCtx, workerCtx, namespace = initializeContextAndNamespace()
|
|
})
|
|
|
|
AfterEach(func() {
|
|
cleanUpNamespace(hubCtx, workerCtx, namespace)
|
|
})
|
|
|
|
It("Test deploying each component to the cluster resolved from its own topology policy", func() {
|
|
app := &v1beta1.Application{}
|
|
bs, err := os.ReadFile("./testdata/app/app-deploy-components.yaml")
|
|
Expect(err).Should(Succeed())
|
|
Expect(yaml.Unmarshal(bs, app)).Should(Succeed())
|
|
app.SetNamespace(namespace)
|
|
Eventually(func(g Gomega) {
|
|
g.Expect(k8sClient.Create(context.Background(), app)).Should(Succeed())
|
|
}).WithPolling(2 * time.Second).WithTimeout(5 * time.Second).Should(Succeed())
|
|
|
|
appKey := client.ObjectKeyFromObject(app)
|
|
Eventually(func(g Gomega) {
|
|
_app := &v1beta1.Application{}
|
|
g.Expect(k8sClient.Get(context.Background(), appKey, _app)).Should(Succeed())
|
|
g.Expect(_app.Status.Phase).Should(Equal(common.ApplicationRunning))
|
|
}).WithPolling(2 * time.Second).WithTimeout(20 * time.Second).Should(Succeed())
|
|
|
|
By("component mapped to the local topology policy only lands on the hub cluster")
|
|
Expect(k8sClient.Get(hubCtx, types.NamespacedName{Namespace: namespace, Name: "cm-on-local"}, &corev1.ConfigMap{})).Should(Succeed())
|
|
Expect(kerrors.IsNotFound(k8sClient.Get(workerCtx, types.NamespacedName{Namespace: namespace, Name: "cm-on-local"}, &corev1.ConfigMap{}))).Should(BeTrue())
|
|
|
|
By("component mapped to the worker topology policy only lands on the worker cluster")
|
|
Expect(k8sClient.Get(workerCtx, types.NamespacedName{Namespace: namespace, Name: "cm-on-worker"}, &corev1.ConfigMap{})).Should(Succeed())
|
|
Expect(kerrors.IsNotFound(k8sClient.Get(hubCtx, types.NamespacedName{Namespace: namespace, Name: "cm-on-worker"}, &corev1.ConfigMap{}))).Should(BeTrue())
|
|
|
|
By("Deleting")
|
|
_app := &v1beta1.Application{}
|
|
Expect(k8sClient.Get(context.Background(), appKey, _app)).Should(Succeed())
|
|
Expect(k8sClient.Delete(context.Background(), _app)).Should(Succeed())
|
|
Eventually(func(g Gomega) {
|
|
g.Expect(kerrors.IsNotFound(k8sClient.Get(context.Background(), appKey, _app))).Should(BeTrue())
|
|
}).WithPolling(2 * time.Second).WithTimeout(20 * time.Second).Should(Succeed())
|
|
Expect(kerrors.IsNotFound(k8sClient.Get(hubCtx, types.NamespacedName{Namespace: namespace, Name: "cm-on-local"}, &corev1.ConfigMap{}))).Should(BeTrue())
|
|
Expect(kerrors.IsNotFound(k8sClient.Get(workerCtx, types.NamespacedName{Namespace: namespace, Name: "cm-on-worker"}, &corev1.ConfigMap{}))).Should(BeTrue())
|
|
})
|
|
|
|
It("Test deploy-components fails fast with a clear message when a component name is wrong", func() {
|
|
app := &v1beta1.Application{}
|
|
bs, err := os.ReadFile("./testdata/app/app-deploy-components-missing.yaml")
|
|
Expect(err).Should(Succeed())
|
|
Expect(yaml.Unmarshal(bs, app)).Should(Succeed())
|
|
app.SetNamespace(namespace)
|
|
Eventually(func(g Gomega) {
|
|
g.Expect(k8sClient.Create(context.Background(), app)).Should(Succeed())
|
|
}).WithPolling(2 * time.Second).WithTimeout(5 * time.Second).Should(Succeed())
|
|
|
|
appKey := client.ObjectKeyFromObject(app)
|
|
Eventually(func(g Gomega) {
|
|
_app := &v1beta1.Application{}
|
|
g.Expect(k8sClient.Get(context.Background(), appKey, _app)).Should(Succeed())
|
|
g.Expect(_app.Status.Workflow).ShouldNot(BeNil())
|
|
g.Expect(len(_app.Status.Workflow.Steps)).ShouldNot(Equal(0))
|
|
g.Expect(_app.Status.Workflow.Steps[0].Phase).Should(Equal(workflowv1alpha1.WorkflowStepPhaseFailed))
|
|
g.Expect(_app.Status.Workflow.Steps[0].Message).Should(ContainSubstring("component(s) not found in application: cm-on-lcal"))
|
|
}).WithPolling(2 * time.Second).WithTimeout(20 * time.Second).Should(Succeed())
|
|
})
|
|
})
|