mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-27 16:17:34 +00:00
Feat: Support kruise rollout (#4243)
* Feat: support kruise rollout Signed-off-by: Somefive <yd219913@alibaba-inc.com> resolve roll back fix add tests Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com> small fix * fix rollback Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com> topology filter by owner reference Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com> fix ci Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com> add comments Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com> fix imports Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com> fix lint * rollback related tests Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com> rename the operator Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com> fix bugs Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com> fix test Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com> fix test * clean args before start Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com> fix test * remove replace go mod Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com> * fix operation tests Signed-off-by: 楚岳 <wangyike.wyk@alibaba-inc.com> Co-authored-by: Somefive <yd219913@alibaba-inc.com>
This commit is contained in:
@@ -280,6 +280,9 @@ func (h *AppHandler) collectHealthStatus(ctx context.Context, wl *appfile.Worklo
|
||||
if err != nil {
|
||||
return nil, false, errors.WithMessagef(err, "app=%s, comp=%s, trait=%s, evaluate status message error", appName, wl.Name, tr.Name)
|
||||
}
|
||||
if status.Message == "" && traitStatus.Message != "" {
|
||||
status.Message = traitStatus.Message
|
||||
}
|
||||
traitStatusList = append(traitStatusList, traitStatus)
|
||||
namespace = appRev.GetNamespace()
|
||||
wl.Ctx.SetCtx(context.WithValue(wl.Ctx.GetCtx(), multicluster.ClusterContextKey, status.Cluster))
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
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 rollout
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
kruisev1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/resourcetracker"
|
||||
velaerrors "github.com/oam-dev/kubevela/pkg/utils/errors"
|
||||
)
|
||||
|
||||
// ClusterRollout rollout in specified cluster
|
||||
type ClusterRollout struct {
|
||||
*kruisev1alpha1.Rollout
|
||||
Cluster string
|
||||
}
|
||||
|
||||
func getAssociatedRollouts(ctx context.Context, cli client.Client, app *v1beta1.Application, withHistoryRTs bool) ([]*ClusterRollout, error) {
|
||||
rootRT, currentRT, historyRTs, _, err := resourcetracker.ListApplicationResourceTrackers(ctx, cli, app)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to list resource trackers")
|
||||
}
|
||||
if !withHistoryRTs {
|
||||
historyRTs = []*v1beta1.ResourceTracker{}
|
||||
}
|
||||
var rollouts []*ClusterRollout
|
||||
for _, rt := range append(historyRTs, rootRT, currentRT) {
|
||||
if rt == nil {
|
||||
continue
|
||||
}
|
||||
for _, mr := range rt.Spec.ManagedResources {
|
||||
if mr.APIVersion == kruisev1alpha1.SchemeGroupVersion.String() && mr.Kind == "Rollout" {
|
||||
rollout := &kruisev1alpha1.Rollout{}
|
||||
if err = cli.Get(multicluster.ContextWithClusterName(ctx, mr.Cluster), k8stypes.NamespacedName{Namespace: mr.Namespace, Name: mr.Name}, rollout); err != nil {
|
||||
if multicluster.IsNotFoundOrClusterNotExists(err) || velaerrors.IsCRDNotExists(err) {
|
||||
continue
|
||||
}
|
||||
return nil, errors.Wrapf(err, "failed to get kruise rollout %s/%s in cluster %s", mr.Namespace, mr.Name, mr.Cluster)
|
||||
}
|
||||
rollouts = append(rollouts, &ClusterRollout{Rollout: rollout, Cluster: mr.Cluster})
|
||||
}
|
||||
}
|
||||
}
|
||||
return rollouts, nil
|
||||
}
|
||||
|
||||
// SuspendRollout find all rollouts associated with the application (including history RTs) and resume them
|
||||
func SuspendRollout(ctx context.Context, cli client.Client, app *v1beta1.Application, writer io.Writer) error {
|
||||
rollouts, err := getAssociatedRollouts(ctx, cli, app, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range rollouts {
|
||||
rollout := rollouts[i]
|
||||
if rollout.Status.Phase == kruisev1alpha1.RolloutPhaseProgressing && !rollout.Spec.Strategy.Paused {
|
||||
_ctx := multicluster.ContextWithClusterName(ctx, rollout.Cluster)
|
||||
rolloutKey := client.ObjectKeyFromObject(rollout.Rollout)
|
||||
if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
if err = cli.Get(_ctx, rolloutKey, rollout.Rollout); err != nil {
|
||||
return err
|
||||
}
|
||||
if rollout.Status.Phase == kruisev1alpha1.RolloutPhaseProgressing && !rollout.Spec.Strategy.Paused {
|
||||
rollout.Spec.Strategy.Paused = true
|
||||
if err = cli.Update(_ctx, rollout.Rollout); err != nil {
|
||||
return err
|
||||
}
|
||||
if writer != nil {
|
||||
_, _ = writer.Write([]byte(fmt.Sprintf("Rollout %s/%s in cluster %s suspended.\n", rollout.Namespace, rollout.Name, rollout.Cluster)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "failed to suspend rollout %s/%s in cluster %s", rollout.Namespace, rollout.Name, rollout.Cluster)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResumeRollout find all rollouts associated with the application (in the current RT) and resume them
|
||||
func ResumeRollout(ctx context.Context, cli client.Client, app *v1beta1.Application, writer io.Writer) (bool, error) {
|
||||
rollouts, err := getAssociatedRollouts(ctx, cli, app, false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
modified := false
|
||||
for i := range rollouts {
|
||||
rollout := rollouts[i]
|
||||
if rollout.Spec.Strategy.Paused || (rollout.Status.CanaryStatus != nil && rollout.Status.CanaryStatus.CurrentStepState == kruisev1alpha1.CanaryStepStatePaused) {
|
||||
_ctx := multicluster.ContextWithClusterName(ctx, rollout.Cluster)
|
||||
rolloutKey := client.ObjectKeyFromObject(rollout.Rollout)
|
||||
resumed := false
|
||||
if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
if err = cli.Get(_ctx, rolloutKey, rollout.Rollout); err != nil {
|
||||
return err
|
||||
}
|
||||
if rollout.Spec.Strategy.Paused {
|
||||
rollout.Spec.Strategy.Paused = false
|
||||
if err = cli.Update(_ctx, rollout.Rollout); err != nil {
|
||||
return err
|
||||
}
|
||||
resumed = true
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return false, errors.Wrapf(err, "failed to resume rollout %s/%s in cluster %s", rollout.Namespace, rollout.Name, rollout.Cluster)
|
||||
}
|
||||
if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
if err = cli.Get(_ctx, rolloutKey, rollout.Rollout); err != nil {
|
||||
return err
|
||||
}
|
||||
if rollout.Status.CanaryStatus != nil && rollout.Status.CanaryStatus.CurrentStepState == kruisev1alpha1.CanaryStepStatePaused {
|
||||
rollout.Status.CanaryStatus.CurrentStepState = kruisev1alpha1.CanaryStepStateReady
|
||||
if err = cli.Status().Update(_ctx, rollout.Rollout); err != nil {
|
||||
return err
|
||||
}
|
||||
resumed = true
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return false, errors.Wrapf(err, "failed to resume rollout %s/%s in cluster %s", rollout.Namespace, rollout.Name, rollout.Cluster)
|
||||
}
|
||||
if resumed {
|
||||
modified = true
|
||||
if writer != nil {
|
||||
_, _ = writer.Write([]byte(fmt.Sprintf("Rollout %s/%s in cluster %s resumed.\n", rollout.Namespace, rollout.Name, rollout.Cluster)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return modified, nil
|
||||
}
|
||||
|
||||
// RollbackRollout find all rollouts associated with the application (in the current RT) and disable the pause field.
|
||||
func RollbackRollout(ctx context.Context, cli client.Client, app *v1beta1.Application, writer io.Writer) (bool, error) {
|
||||
rollouts, err := getAssociatedRollouts(ctx, cli, app, false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
modified := false
|
||||
for i := range rollouts {
|
||||
rollout := rollouts[i]
|
||||
if rollout.Spec.Strategy.Paused || (rollout.Status.CanaryStatus != nil && rollout.Status.CanaryStatus.CurrentStepState == kruisev1alpha1.CanaryStepStatePaused) {
|
||||
_ctx := multicluster.ContextWithClusterName(ctx, rollout.Cluster)
|
||||
rolloutKey := client.ObjectKeyFromObject(rollout.Rollout)
|
||||
resumed := false
|
||||
if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
if err = cli.Get(_ctx, rolloutKey, rollout.Rollout); err != nil {
|
||||
return err
|
||||
}
|
||||
if rollout.Spec.Strategy.Paused {
|
||||
rollout.Spec.Strategy.Paused = false
|
||||
if err = cli.Update(_ctx, rollout.Rollout); err != nil {
|
||||
return err
|
||||
}
|
||||
resumed = true
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return false, errors.Wrapf(err, "failed to rollback rollout %s/%s in cluster %s", rollout.Namespace, rollout.Name, rollout.Cluster)
|
||||
}
|
||||
if resumed {
|
||||
modified = true
|
||||
if writer != nil {
|
||||
_, _ = writer.Write([]byte(fmt.Sprintf("Rollout %s/%s in cluster %s rollback.\n", rollout.Namespace, rollout.Name, rollout.Cluster)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return modified, nil
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
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 rollout
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
kruisev1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"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/util"
|
||||
)
|
||||
|
||||
var _ = Describe("Kruise rollout test", func() {
|
||||
ctx := context.Background()
|
||||
BeforeEach(func() {
|
||||
Expect(k8sClient.Create(ctx, rollout.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, rt.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, app.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
})
|
||||
|
||||
It("test get associated rollout func", func() {
|
||||
rollouts, err := getAssociatedRollouts(ctx, k8sClient, &app, false)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(rollouts)).Should(BeEquivalentTo(1))
|
||||
})
|
||||
|
||||
It("Suspend rollout", func() {
|
||||
r := kruisev1alpha1.Rollout{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "my-rollout"}, &r)).Should(BeNil())
|
||||
r.Status.Phase = kruisev1alpha1.RolloutPhaseProgressing
|
||||
Expect(k8sClient.Status().Update(ctx, &r)).Should(BeNil())
|
||||
Expect(SuspendRollout(ctx, k8sClient, &app, nil))
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "my-rollout"}, &r))
|
||||
Expect(r.Spec.Strategy.Paused).Should(BeEquivalentTo(true))
|
||||
})
|
||||
|
||||
It("Resume rollout", func() {
|
||||
r := kruisev1alpha1.Rollout{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "my-rollout"}, &r)).Should(BeNil())
|
||||
Expect(r.Spec.Strategy.Paused).Should(BeEquivalentTo(true))
|
||||
Expect(ResumeRollout(ctx, k8sClient, &app, nil))
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "my-rollout"}, &r))
|
||||
Expect(r.Spec.Strategy.Paused).Should(BeEquivalentTo(false))
|
||||
})
|
||||
|
||||
It("Rollback rollout", func() {
|
||||
r := kruisev1alpha1.Rollout{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "my-rollout"}, &r)).Should(BeNil())
|
||||
r.Spec.Strategy.Paused = true
|
||||
Expect(k8sClient.Update(ctx, &r)).Should(BeNil())
|
||||
Expect(RollbackRollout(ctx, k8sClient, &app, nil))
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "my-rollout"}, &r))
|
||||
Expect(r.Spec.Strategy.Paused).Should(BeEquivalentTo(false))
|
||||
})
|
||||
})
|
||||
|
||||
var app = v1beta1.Application{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
Kind: "Application",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "rollout-app",
|
||||
Namespace: "default",
|
||||
Generation: 1,
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{},
|
||||
},
|
||||
}
|
||||
|
||||
var rt = v1beta1.ResourceTracker{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
Kind: "ResourceTracker",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "rollout-app",
|
||||
Labels: map[string]string{
|
||||
"app.oam.dev/appRevision": "rollout-app-v1",
|
||||
"app.oam.dev/name": "rollout-app",
|
||||
"app.oam.dev/namespace": "default",
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ResourceTrackerSpec{
|
||||
ApplicationGeneration: 1,
|
||||
Type: v1beta1.ResourceTrackerTypeVersioned,
|
||||
ManagedResources: []v1beta1.ManagedResource{
|
||||
{
|
||||
ClusterObjectReference: common.ClusterObjectReference{
|
||||
ObjectReference: v1.ObjectReference{
|
||||
APIVersion: "rollouts.kruise.io/v1alpha1",
|
||||
Kind: "Rollout",
|
||||
Name: "my-rollout",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
OAMObjectReference: common.OAMObjectReference{
|
||||
Component: "my-rollout",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var rollout = kruisev1alpha1.Rollout{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "rollouts.kruise.io/v1alpha1",
|
||||
Kind: "Rollout",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-rollout",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: kruisev1alpha1.RolloutSpec{
|
||||
ObjectRef: kruisev1alpha1.ObjectRef{
|
||||
WorkloadRef: &kruisev1alpha1.WorkloadRef{
|
||||
APIVersion: "appsv1",
|
||||
Kind: "Deployment",
|
||||
Name: "canary-demo",
|
||||
},
|
||||
},
|
||||
Strategy: kruisev1alpha1.RolloutStrategy{
|
||||
Canary: &kruisev1alpha1.CanaryStrategy{
|
||||
Steps: []kruisev1alpha1.CanaryStep{
|
||||
{
|
||||
Weight: 30,
|
||||
},
|
||||
},
|
||||
},
|
||||
Paused: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
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 rollout
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/client-go/discovery"
|
||||
ocmclusterv1 "open-cluster-management.io/api/cluster/v1"
|
||||
ocmclusterv1alpha1 "open-cluster-management.io/api/cluster/v1alpha1"
|
||||
ocmworkv1 "open-cluster-management.io/api/work/v1"
|
||||
|
||||
v12 "k8s.io/api/core/v1"
|
||||
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
kruisev1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
||||
|
||||
coreoam "github.com/oam-dev/kubevela/apis/core.oam.dev"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/packages"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
var cfg *rest.Config
|
||||
var scheme *runtime.Scheme
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
var dm discoverymapper.DiscoveryMapper
|
||||
var pd *packages.PackageDiscover
|
||||
var testns string
|
||||
var dc *discovery.DiscoveryClient
|
||||
|
||||
func TestAddon(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecsWithDefaultAndCustomReporters(t,
|
||||
"Kruise rollout Suite test",
|
||||
[]Reporter{printer.NewlineReporter{}})
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
logf.SetLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter)))
|
||||
By("bootstrapping test environment")
|
||||
useExistCluster := false
|
||||
testEnv = &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "..", "charts", "vela-core", "crds"), filepath.Join("", "testdata")},
|
||||
UseExistingCluster: &useExistCluster,
|
||||
}
|
||||
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
scheme = runtime.NewScheme()
|
||||
Expect(coreoam.AddToScheme(scheme)).NotTo(HaveOccurred())
|
||||
Expect(clientgoscheme.AddToScheme(scheme)).NotTo(HaveOccurred())
|
||||
Expect(crdv1.AddToScheme(scheme)).NotTo(HaveOccurred())
|
||||
_ = ocmclusterv1alpha1.Install(scheme)
|
||||
_ = ocmclusterv1.Install(scheme)
|
||||
_ = ocmworkv1.Install(scheme)
|
||||
_ = kruisev1alpha1.AddToScheme(scheme)
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
dc, err = discovery.NewDiscoveryClientForConfig(cfg)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(dc).ShouldNot(BeNil())
|
||||
|
||||
dm, err = discoverymapper.New(cfg)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(dm).ToNot(BeNil())
|
||||
pd, err = packages.NewPackageDiscover(cfg)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(pd).ToNot(BeNil())
|
||||
testns = "vela-system"
|
||||
Expect(k8sClient.Create(context.Background(),
|
||||
&v12.Namespace{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Namespace"}, ObjectMeta: metav1.ObjectMeta{
|
||||
Name: testns,
|
||||
}}))
|
||||
|
||||
close(done)
|
||||
}, 120)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
Vendored
+290
@@ -0,0 +1,290 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.7.0
|
||||
creationTimestamp: null
|
||||
name: rollouts.rollouts.kruise.io
|
||||
spec:
|
||||
group: rollouts.kruise.io
|
||||
names:
|
||||
kind: Rollout
|
||||
listKind: RolloutList
|
||||
plural: rollouts
|
||||
singular: rollout
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- description: The rollout status phase
|
||||
jsonPath: .status.phase
|
||||
name: STATUS
|
||||
type: string
|
||||
- description: The rollout canary status step
|
||||
jsonPath: .status.canaryStatus.currentStepIndex
|
||||
name: CANARY_STEP
|
||||
type: integer
|
||||
- description: The rollout canary status step state
|
||||
jsonPath: .status.canaryStatus.currentStepState
|
||||
name: CANARY_STATE
|
||||
type: string
|
||||
- description: The rollout canary status message
|
||||
jsonPath: .status.message
|
||||
name: MESSAGE
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: AGE
|
||||
type: date
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Rollout is the Schema for the rollouts API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: RolloutSpec defines the desired state of Rollout
|
||||
properties:
|
||||
objectRef:
|
||||
description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||
Important: Run "make" to regenerate code after modifying this file
|
||||
ObjectRef indicates workload'
|
||||
properties:
|
||||
workloadRef:
|
||||
description: WorkloadRef contains enough information to let you
|
||||
identify a workload for Rollout Batch release of the bypass
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API Version of the referent
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referent
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referent
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
strategy:
|
||||
description: rollout strategy
|
||||
properties:
|
||||
canary:
|
||||
description: CanaryStrategy defines parameters for a Replica Based
|
||||
Canary
|
||||
properties:
|
||||
steps:
|
||||
description: Steps define the order of phases to execute release
|
||||
in batches(20%, 40%, 60%, 80%, 100%)
|
||||
items:
|
||||
description: CanaryStep defines a step of a canary workload.
|
||||
properties:
|
||||
pause:
|
||||
description: Pause defines a pause stage for a rollout,
|
||||
manual or auto
|
||||
properties:
|
||||
duration:
|
||||
description: Duration the amount of time to wait
|
||||
before moving to the next step.
|
||||
format: int32
|
||||
type: integer
|
||||
type: object
|
||||
replicas:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: 'Replicas is the number of expected canary
|
||||
pods in this batch it can be an absolute number (ex:
|
||||
5) or a percentage of total pods.'
|
||||
x-kubernetes-int-or-string: true
|
||||
weight:
|
||||
description: SetWeight sets what percentage of the canary
|
||||
pods should receive
|
||||
format: int32
|
||||
type: integer
|
||||
type: object
|
||||
type: array
|
||||
trafficRoutings:
|
||||
description: TrafficRoutings hosts all the supported service
|
||||
meshes supported to enable more fine-grained traffic routing
|
||||
todo current only support one
|
||||
items:
|
||||
description: TrafficRouting hosts all the different configuration
|
||||
for supported service meshes to enable more fine-grained
|
||||
traffic routing
|
||||
properties:
|
||||
gracePeriodSeconds:
|
||||
description: Optional duration in seconds the traffic
|
||||
provider(e.g. nginx ingress controller) consumes the
|
||||
service, ingress configuration changes gracefully.
|
||||
format: int32
|
||||
type: integer
|
||||
ingress:
|
||||
description: Ingress holds Ingress specific configuration
|
||||
to route traffic, e.g. Nginx, Alb.
|
||||
properties:
|
||||
name:
|
||||
description: Name refers to the name of an `Ingress`
|
||||
resource in the same namespace as the `Rollout`
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
service:
|
||||
description: Service holds the name of a service which
|
||||
selects pods with stable version and don't select
|
||||
any pods with canary version.
|
||||
type: string
|
||||
type:
|
||||
description: nginx, alb, istio etc.
|
||||
type: string
|
||||
required:
|
||||
- service
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
paused:
|
||||
description: Paused indicates that the Rollout is paused. Default
|
||||
value is false
|
||||
type: boolean
|
||||
type: object
|
||||
required:
|
||||
- objectRef
|
||||
- strategy
|
||||
type: object
|
||||
status:
|
||||
description: RolloutStatus defines the observed state of Rollout
|
||||
properties:
|
||||
canaryStatus:
|
||||
description: Canary describes the state of the canary rollout
|
||||
properties:
|
||||
canaryReadyReplicas:
|
||||
description: CanaryReadyReplicas the numbers of ready canary revision
|
||||
pods
|
||||
format: int32
|
||||
type: integer
|
||||
canaryReplicas:
|
||||
description: CanaryReplicas the numbers of canary revision pods
|
||||
format: int32
|
||||
type: integer
|
||||
canaryRevision:
|
||||
description: CanaryRevision is calculated by rollout based on
|
||||
podTemplateHash, and the internal logic flow uses It may be
|
||||
different from rs podTemplateHash in different k8s versions,
|
||||
so it cannot be used as service selector label
|
||||
type: string
|
||||
canaryService:
|
||||
description: CanaryService holds the name of a service which selects
|
||||
pods with canary version and don't select any pods with stable
|
||||
version.
|
||||
type: string
|
||||
currentStepIndex:
|
||||
description: CurrentStepIndex defines the current step of the
|
||||
rollout is on. If the current step index is null, the controller
|
||||
will execute the rollout.
|
||||
format: int32
|
||||
type: integer
|
||||
currentStepState:
|
||||
type: string
|
||||
lastReadyTime:
|
||||
description: The last time this step pods is ready.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
observedWorkloadGeneration:
|
||||
description: observedWorkloadGeneration is the most recent generation
|
||||
observed for this Rollout ref workload generation.
|
||||
format: int64
|
||||
type: integer
|
||||
podTemplateHash:
|
||||
description: pod template hash is used as service selector label
|
||||
type: string
|
||||
rolloutHash:
|
||||
description: RolloutHash from rollout.spec object
|
||||
type: string
|
||||
required:
|
||||
- canaryReadyReplicas
|
||||
- canaryReplicas
|
||||
- canaryService
|
||||
- currentStepState
|
||||
- podTemplateHash
|
||||
type: object
|
||||
conditions:
|
||||
description: Conditions a list of conditions a rollout can have.
|
||||
items:
|
||||
description: RolloutCondition describes the state of a rollout at
|
||||
a certain point.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: Last time the condition transitioned from one status
|
||||
to another.
|
||||
format: date-time
|
||||
type: string
|
||||
lastUpdateTime:
|
||||
description: The last time this condition was updated.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A human readable message indicating details about
|
||||
the transition.
|
||||
type: string
|
||||
reason:
|
||||
description: The reason for the condition's last transition.
|
||||
type: string
|
||||
status:
|
||||
description: Phase of the condition, one of True, False, Unknown.
|
||||
type: string
|
||||
type:
|
||||
description: Type of rollout condition.
|
||||
type: string
|
||||
required:
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
message:
|
||||
description: Message provides details on why the rollout is in its
|
||||
current phase
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration is the most recent generation observed
|
||||
for this Rollout.
|
||||
format: int64
|
||||
type: integer
|
||||
phase:
|
||||
description: BlueGreenStatus *BlueGreenStatus `json:"blueGreenStatus,omitempty"`
|
||||
Phase is the rollout phase.
|
||||
type: string
|
||||
stableRevision:
|
||||
description: CanaryRevision the hash of the canary pod template CanaryRevision
|
||||
string `json:"canaryRevision,omitempty"` StableRevision indicates
|
||||
the revision pods that has successfully rolled out
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
@@ -37,6 +37,7 @@ import (
|
||||
"github.com/hashicorp/hcl/v2/hclparse"
|
||||
"github.com/oam-dev/terraform-config-inspect/tfconfig"
|
||||
kruise "github.com/openkruise/kruise-api/apps/v1alpha1"
|
||||
kruisev1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
||||
errors2 "github.com/pkg/errors"
|
||||
certmanager "github.com/wonderflow/cert-manager-api/pkg/apis/certmanager/v1"
|
||||
istioclientv1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1"
|
||||
@@ -102,6 +103,7 @@ func init() {
|
||||
_ = ocmworkv1.Install(Scheme)
|
||||
_ = clustergatewayapi.AddToScheme(Scheme)
|
||||
_ = metricsV1beta1api.AddToScheme(Scheme)
|
||||
_ = kruisev1alpha1.AddToScheme(Scheme)
|
||||
_ = prismclusterv1alpha1.AddToScheme(Scheme)
|
||||
// +kubebuilder:scaffold:scheme
|
||||
}
|
||||
|
||||
@@ -89,7 +89,8 @@ func init() {
|
||||
{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"}: nil,
|
||||
{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "RoleBinding"}: nil,
|
||||
},
|
||||
DefaultGenListOptionFunc: helmRelease2AnyListOption,
|
||||
DefaultGenListOptionFunc: helmRelease2AnyListOption,
|
||||
DisableFilterByOwnerReference: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +118,8 @@ type ChildrenResourcesRule struct {
|
||||
CareResource map[ResourceType]genListOptionFunc
|
||||
// if specified genListOptionFunc is nil will use use default genListOptionFunc to generate listOption.
|
||||
DefaultGenListOptionFunc genListOptionFunc
|
||||
// DisableFilterByOwnerReference means don't use parent resource's UID filter the result.
|
||||
DisableFilterByOwnerReference bool
|
||||
}
|
||||
|
||||
type genListOptionFunc func(unstructured.Unstructured) (client.ListOptions, error)
|
||||
@@ -618,7 +621,7 @@ func fetchObjectWithResourceTreeNode(ctx context.Context, cluster string, k8sCli
|
||||
}
|
||||
|
||||
func listItemByRule(clusterCTX context.Context, k8sClient client.Client, resource ResourceType,
|
||||
parentObject unstructured.Unstructured, specifiedFunc genListOptionFunc, defaultFunc genListOptionFunc) ([]unstructured.Unstructured, error) {
|
||||
parentObject unstructured.Unstructured, specifiedFunc genListOptionFunc, defaultFunc genListOptionFunc, disableFilterByOwner bool) ([]unstructured.Unstructured, error) {
|
||||
|
||||
itemList := unstructured.UnstructuredList{}
|
||||
itemList.SetAPIVersion(resource.APIVersion)
|
||||
@@ -657,6 +660,20 @@ func listItemByRule(clusterCTX context.Context, k8sClient client.Client, resourc
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !disableFilterByOwner {
|
||||
var res []unstructured.Unstructured
|
||||
for _, item := range itemList.Items {
|
||||
if len(item.GetOwnerReferences()) == 0 {
|
||||
res = append(res, item)
|
||||
}
|
||||
for _, reference := range item.GetOwnerReferences() {
|
||||
if reference.UID == parentObject.GetUID() {
|
||||
res = append(res, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
return itemList.Items, nil
|
||||
}
|
||||
|
||||
@@ -676,7 +693,7 @@ func iteratorChildResources(ctx context.Context, cluster string, k8sClient clien
|
||||
var resList []*types.ResourceTreeNode
|
||||
for resource, specifiedFunc := range rules.CareResource {
|
||||
clusterCTX := multicluster.ContextWithClusterName(ctx, cluster)
|
||||
items, err := listItemByRule(clusterCTX, k8sClient, resource, *parentObject, specifiedFunc, rules.DefaultGenListOptionFunc)
|
||||
items, err := listItemByRule(clusterCTX, k8sClient, resource, *parentObject, specifiedFunc, rules.DefaultGenListOptionFunc, rules.DisableFilterByOwnerReference)
|
||||
if err != nil {
|
||||
if meta.IsNoMatchError(err) || runtime.IsNotRegisteredError(err) {
|
||||
log.Logger.Errorf("error to list subresources: %s err: %v", resource.Kind, err)
|
||||
|
||||
@@ -1266,14 +1266,14 @@ var _ = Describe("unit-test to e2e test", func() {
|
||||
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(deploy1.DeepCopy())
|
||||
Expect(err).Should(BeNil())
|
||||
items, err := listItemByRule(ctx, k8sClient, ResourceType{APIVersion: "apps/v1", Kind: "ReplicaSet"}, unstructured.Unstructured{Object: u},
|
||||
deploy2RsLabelListOption, nil)
|
||||
deploy2RsLabelListOption, nil, true)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(items)).Should(BeEquivalentTo(2))
|
||||
|
||||
u2, err := runtime.DefaultUnstructuredConverter.ToUnstructured(deploy2.DeepCopy())
|
||||
Expect(err).Should(BeNil())
|
||||
items2, err := listItemByRule(ctx, k8sClient, ResourceType{APIVersion: "apps/v1", Kind: "ReplicaSet"}, unstructured.Unstructured{Object: u2},
|
||||
nil, deploy2RsLabelListOption)
|
||||
nil, deploy2RsLabelListOption, true)
|
||||
Expect(len(items2)).Should(BeEquivalentTo(1))
|
||||
|
||||
// test use ownerReference UId to filter
|
||||
@@ -1285,7 +1285,7 @@ var _ = Describe("unit-test to e2e test", func() {
|
||||
Expect(k8sClient.Get(ctx, types2.NamespacedName{Namespace: u3.GetNamespace(), Name: u3.GetName()}, &u3))
|
||||
Expect(err).Should(BeNil())
|
||||
items3, err := listItemByRule(ctx, k8sClient, ResourceType{APIVersion: "v1", Kind: "Pod"}, u3,
|
||||
nil, nil)
|
||||
nil, nil, true)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(items3)).Should(BeEquivalentTo(1))
|
||||
})
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
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 operation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/client-go/util/retry"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"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/apiserver/domain/service"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1alpha2/application"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/utils"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/resourcetracker"
|
||||
"github.com/oam-dev/kubevela/pkg/rollout"
|
||||
errors3 "github.com/oam-dev/kubevela/pkg/utils/errors"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// WorkflowOperator is opratior handler for workflow's resume/rollback/restart
|
||||
type WorkflowOperator interface {
|
||||
Suspend(ctx context.Context, app *v1beta1.Application) error
|
||||
Resume(ctx context.Context, app *v1beta1.Application) error
|
||||
Rollback(ctx context.Context, app *v1beta1.Application) error
|
||||
Restart(ctx context.Context, app *v1beta1.Application) error
|
||||
Terminate(ctx context.Context, app *v1beta1.Application) error
|
||||
}
|
||||
|
||||
// NewWorkflowOperator get an workflow operator with k8sClient and ioWriter(optional, useful for cli)
|
||||
func NewWorkflowOperator(cli client.Client, w io.Writer) WorkflowOperator {
|
||||
return wfOperator{cli: cli, outputWriter: w}
|
||||
}
|
||||
|
||||
type wfOperator struct {
|
||||
cli client.Client
|
||||
outputWriter io.Writer
|
||||
}
|
||||
|
||||
// Suspend a running workflow
|
||||
func (wo wfOperator) Suspend(ctx context.Context, app *v1beta1.Application) error {
|
||||
if app.Status.Workflow == nil {
|
||||
return fmt.Errorf("the workflow in application is not running")
|
||||
}
|
||||
var err error
|
||||
if err = rollout.SuspendRollout(context.Background(), wo.cli, app, wo.outputWriter); err != nil {
|
||||
return err
|
||||
}
|
||||
appKey := client.ObjectKeyFromObject(app)
|
||||
if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
if err := wo.cli.Get(ctx, appKey, app); err != nil {
|
||||
return err
|
||||
}
|
||||
// set the workflow suspend to true
|
||||
app.Status.Workflow.Suspend = true
|
||||
return wo.cli.Status().Patch(ctx, app, client.Merge)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return wo.writeOutputF("Successfully suspend workflow: %s\n", app.Name)
|
||||
}
|
||||
|
||||
// Resume a suspending workflow
|
||||
func (wo wfOperator) Resume(ctx context.Context, app *v1beta1.Application) error {
|
||||
if app.Status.Workflow == nil {
|
||||
return fmt.Errorf("the workflow in application is not running")
|
||||
}
|
||||
if app.Status.Workflow.Terminated {
|
||||
return fmt.Errorf("can not resume a terminated workflow")
|
||||
}
|
||||
|
||||
var rolloutResumed bool
|
||||
var err error
|
||||
|
||||
if rolloutResumed, err = rollout.ResumeRollout(context.Background(), wo.cli, app, wo.outputWriter); err != nil {
|
||||
return err
|
||||
}
|
||||
if !rolloutResumed && !app.Status.Workflow.Suspend {
|
||||
return wo.writeOutput("the workflow is not suspending")
|
||||
}
|
||||
|
||||
if app.Status.Workflow.Suspend {
|
||||
if err = service.ResumeWorkflow(ctx, wo.cli, app); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Rollback a running in middle state workflow.
|
||||
//nolint
|
||||
func (wo wfOperator) Rollback(ctx context.Context, app *v1beta1.Application) error {
|
||||
if oam.GetPublishVersion(app) == "" {
|
||||
return fmt.Errorf("app without public version cannot rollback")
|
||||
}
|
||||
|
||||
appRevs, err := application.GetSortedAppRevisions(ctx, wo.cli, app.Name, app.Namespace)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to list revisions for application %s/%s", app.Namespace, app.Name)
|
||||
}
|
||||
|
||||
// find succeeded revision to rollback
|
||||
var rev *v1beta1.ApplicationRevision
|
||||
var outdatedRev []*v1beta1.ApplicationRevision
|
||||
for i := range appRevs {
|
||||
candidate := appRevs[len(appRevs)-i-1]
|
||||
_rev := candidate.DeepCopy()
|
||||
if !candidate.Status.Succeeded || oam.GetPublishVersion(_rev) == "" {
|
||||
outdatedRev = append(outdatedRev, _rev)
|
||||
continue
|
||||
}
|
||||
rev = _rev
|
||||
break
|
||||
}
|
||||
if rev == nil {
|
||||
return errors.Errorf("failed to find previous succeeded revision for application %s/%s", app.Namespace, app.Name)
|
||||
}
|
||||
publishVersion := oam.GetPublishVersion(rev)
|
||||
revisionNumber, err := utils.ExtractRevision(rev.Name)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to extract revision number from revision %s", rev.Name)
|
||||
}
|
||||
_, currentRT, historyRTs, _, err := resourcetracker.ListApplicationResourceTrackers(ctx, wo.cli, app)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to list resource trackers for application %s/%s", app.Namespace, app.Name)
|
||||
}
|
||||
var matchRT *v1beta1.ResourceTracker
|
||||
for _, rt := range append(historyRTs, currentRT) {
|
||||
if rt == nil {
|
||||
continue
|
||||
}
|
||||
labels := rt.GetLabels()
|
||||
if labels != nil && labels[oam.LabelAppRevision] == rev.Name {
|
||||
matchRT = rt.DeepCopy()
|
||||
}
|
||||
}
|
||||
if matchRT == nil {
|
||||
return errors.Errorf("cannot find resource tracker for previous revision %s, unable to rollback", rev.Name)
|
||||
}
|
||||
if matchRT.DeletionTimestamp != nil {
|
||||
return errors.Errorf("previous revision %s is being recycled, unable to rollback", rev.Name)
|
||||
}
|
||||
err = wo.writeOutput("Find succeeded application revision %s (PublishVersion: %s) to rollback.\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appKey := client.ObjectKeyFromObject(app)
|
||||
// rollback application spec and freeze
|
||||
controllerRequirement, err := utils.FreezeApplication(ctx, wo.cli, app, func() {
|
||||
app.Spec = rev.Spec.Application.Spec
|
||||
oam.SetPublishVersion(app, publishVersion)
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to rollback application spec to revision %s (PublishVersion: %s)", rev.Name, publishVersion)
|
||||
}
|
||||
err = wo.writeOutput("Application spec rollback successfully.\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// rollback application status
|
||||
if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
if err = wo.cli.Get(ctx, appKey, app); err != nil {
|
||||
return err
|
||||
}
|
||||
app.Status.Workflow = rev.Status.Workflow
|
||||
app.Status.Services = []common.ApplicationComponentStatus{}
|
||||
app.Status.AppliedResources = []common.ClusterObjectReference{}
|
||||
for _, rsc := range matchRT.Spec.ManagedResources {
|
||||
app.Status.AppliedResources = append(app.Status.AppliedResources, rsc.ClusterObjectReference)
|
||||
}
|
||||
app.Status.LatestRevision = &common.Revision{
|
||||
Name: rev.Name,
|
||||
Revision: int64(revisionNumber),
|
||||
RevisionHash: rev.GetLabels()[oam.LabelAppRevisionHash],
|
||||
}
|
||||
return wo.cli.Status().Update(ctx, app)
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "failed to rollback application status to revision %s (PublishVersion: %s)", rev.Name, publishVersion)
|
||||
}
|
||||
|
||||
err = wo.writeOutput("Application status rollback successfully.\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// update resource tracker generation
|
||||
matchRTKey := client.ObjectKeyFromObject(matchRT)
|
||||
if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
if err = wo.cli.Get(ctx, matchRTKey, matchRT); err != nil {
|
||||
return err
|
||||
}
|
||||
matchRT.Spec.ApplicationGeneration = app.Generation
|
||||
return wo.cli.Update(ctx, matchRT)
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "failed to update application generation in resource tracker")
|
||||
}
|
||||
|
||||
// unfreeze application
|
||||
if err = utils.UnfreezeApplication(ctx, wo.cli, app, nil, controllerRequirement); err != nil {
|
||||
return errors.Wrapf(err, "failed to resume application to restart")
|
||||
}
|
||||
|
||||
rollback, err := rollout.RollbackRollout(ctx, wo.cli, app, wo.outputWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rollback {
|
||||
err = wo.writeOutput("Successfully rollback rollout")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// clean up outdated revisions
|
||||
var errs errors3.ErrorList
|
||||
for _, _rev := range outdatedRev {
|
||||
if err = wo.cli.Delete(ctx, _rev); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if errs.HasError() {
|
||||
return errors.Wrapf(errs, "failed to clean up outdated revisions")
|
||||
}
|
||||
|
||||
err = wo.writeOutput("Application outdated revision cleaned up.\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restart a terminated or finished workflow.
|
||||
func (wo wfOperator) Restart(ctx context.Context, app *v1beta1.Application) error {
|
||||
if app.Status.Workflow == nil {
|
||||
return fmt.Errorf("the workflow in application is not running")
|
||||
}
|
||||
// reset the workflow status to restart the workflow
|
||||
app.Status.Workflow = nil
|
||||
|
||||
if err := wo.cli.Status().Update(context.TODO(), app); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return wo.writeOutputF("Successfully restart workflow: %s\n", app.Name)
|
||||
}
|
||||
|
||||
func (wo wfOperator) Terminate(ctx context.Context, app *v1beta1.Application) error {
|
||||
if err := service.TerminateWorkflow(context.TODO(), wo.cli, app); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wo wfOperator) writeOutput(str string) error {
|
||||
if wo.outputWriter == nil {
|
||||
return nil
|
||||
}
|
||||
_, err := wo.outputWriter.Write([]byte(str))
|
||||
return err
|
||||
}
|
||||
|
||||
func (wo wfOperator) writeOutputF(format string, a ...interface{}) error {
|
||||
if wo.outputWriter == nil {
|
||||
return nil
|
||||
}
|
||||
_, err := wo.outputWriter.Write([]byte(fmt.Sprintf(format, a...)))
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
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 operation
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
kruisev1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"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"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
var _ = Describe("Kruise rollout test", func() {
|
||||
ctx := context.Background()
|
||||
BeforeEach(func() {
|
||||
Expect(k8sClient.Create(ctx, myRollout.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, rt.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
Expect(k8sClient.Create(ctx, app.DeepCopy())).Should(SatisfyAny(BeNil(), util.AlreadyExistMatcher{}))
|
||||
})
|
||||
|
||||
It("Suspend workflow", func() {
|
||||
checkApp := v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
checkApp.Status.Workflow = &common.WorkflowStatus{Suspend: false, StartTime: metav1.Now()}
|
||||
Expect(k8sClient.Status().Update(ctx, &checkApp)).Should(BeNil())
|
||||
operator := NewWorkflowOperator(k8sClient, nil)
|
||||
checkApp = v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
Expect(operator.Suspend(ctx, checkApp.DeepCopy())).Should(BeNil())
|
||||
checkApp = v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
Expect(checkApp.Status.Workflow.Suspend).Should(BeEquivalentTo(true))
|
||||
})
|
||||
|
||||
It("Resume workflow", func() {
|
||||
checkApp := v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
operator := NewWorkflowOperator(k8sClient, nil)
|
||||
Expect(operator.Resume(ctx, &checkApp)).Should(BeNil())
|
||||
checkApp = v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
Expect(checkApp.Status.Workflow.Suspend).Should(BeEquivalentTo(false))
|
||||
})
|
||||
|
||||
It("Terminate workflow", func() {
|
||||
checkApp := v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
operator := NewWorkflowOperator(k8sClient, nil)
|
||||
Expect(operator.Terminate(ctx, &checkApp)).Should(BeNil())
|
||||
checkApp = v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
Expect(checkApp.Status.Workflow.Terminated).Should(BeEquivalentTo(true))
|
||||
})
|
||||
|
||||
It("Restart workflow", func() {
|
||||
checkApp := v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
operator := NewWorkflowOperator(k8sClient, nil)
|
||||
Expect(operator.Restart(ctx, &checkApp)).Should(BeNil())
|
||||
checkApp = v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
Expect(checkApp.Status.Workflow).Should(BeNil())
|
||||
})
|
||||
|
||||
It("Rollback workflow", func() {
|
||||
Expect(k8sClient.Create(ctx, &appRev)).Should(BeNil())
|
||||
checkAppRev := v1beta1.ApplicationRevision{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app-v1"}, &checkAppRev)).Should(BeNil())
|
||||
checkAppRev.Status.Succeeded = true
|
||||
Expect(k8sClient.Status().Update(ctx, checkAppRev.DeepCopy())).Should(BeNil())
|
||||
|
||||
checkApp := v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
checkApp.Annotations = map[string]string{
|
||||
oam.AnnotationPublishVersion: "v2",
|
||||
}
|
||||
operator := NewWorkflowOperator(k8sClient, nil)
|
||||
Expect(operator.Rollback(ctx, checkApp.DeepCopy())).Should(BeNil())
|
||||
|
||||
checkApp = v1beta1.Application{}
|
||||
Expect(k8sClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "opt-app"}, &checkApp)).Should(BeNil())
|
||||
// must rollback to v1
|
||||
Expect(oam.GetPublishVersion(&checkApp)).Should(BeEquivalentTo("v1"))
|
||||
Expect(checkApp.Status.LatestRevision.Name).Should(BeEquivalentTo("opt-app-v1"))
|
||||
Expect(checkApp.Status.LatestRevision.Revision).Should(BeEquivalentTo(1))
|
||||
})
|
||||
})
|
||||
|
||||
var app = v1beta1.Application{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
Kind: "Application",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "opt-app",
|
||||
Namespace: "default",
|
||||
Generation: 1,
|
||||
Labels: map[string]string{
|
||||
oam.AnnotationPublishVersion: "v2",
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{},
|
||||
},
|
||||
}
|
||||
|
||||
var rt = v1beta1.ResourceTracker{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
Kind: "ResourceTracker",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "rollout-app",
|
||||
Labels: map[string]string{
|
||||
"app.oam.dev/appRevision": "opt-app-v1",
|
||||
"app.oam.dev/name": "opt-app",
|
||||
"app.oam.dev/namespace": "default",
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ResourceTrackerSpec{
|
||||
ApplicationGeneration: 1,
|
||||
Type: v1beta1.ResourceTrackerTypeVersioned,
|
||||
ManagedResources: []v1beta1.ManagedResource{
|
||||
{
|
||||
ClusterObjectReference: common.ClusterObjectReference{
|
||||
ObjectReference: v1.ObjectReference{
|
||||
APIVersion: "rollouts.kruise.io/v1alpha1",
|
||||
Kind: "Rollout",
|
||||
Name: "my-rollout",
|
||||
Namespace: "default",
|
||||
},
|
||||
},
|
||||
OAMObjectReference: common.OAMObjectReference{
|
||||
Component: "my-rollout",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var appRev = v1beta1.ApplicationRevision{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "core.oam.dev/v1beta1",
|
||||
Kind: "ApplicationRevision",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "opt-app-v1",
|
||||
Namespace: "default",
|
||||
Labels: map[string]string{
|
||||
"app.oam.dev/name": "opt-app",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
oam.AnnotationPublishVersion: "v1",
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ApplicationRevisionSpec{
|
||||
Application: v1beta1.Application{
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var myRollout = kruisev1alpha1.Rollout{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "rollouts.kruise.io/v1alpha1",
|
||||
Kind: "Rollout",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-rollout",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: kruisev1alpha1.RolloutSpec{
|
||||
ObjectRef: kruisev1alpha1.ObjectRef{
|
||||
WorkloadRef: &kruisev1alpha1.WorkloadRef{
|
||||
APIVersion: "appsv1",
|
||||
Kind: "Deployment",
|
||||
Name: "canary-demo",
|
||||
},
|
||||
},
|
||||
Strategy: kruisev1alpha1.RolloutStrategy{
|
||||
Canary: &kruisev1alpha1.CanaryStrategy{
|
||||
Steps: []kruisev1alpha1.CanaryStep{
|
||||
{
|
||||
Weight: 30,
|
||||
},
|
||||
},
|
||||
},
|
||||
Paused: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
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 operation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/client-go/discovery"
|
||||
ocmclusterv1 "open-cluster-management.io/api/cluster/v1"
|
||||
ocmclusterv1alpha1 "open-cluster-management.io/api/cluster/v1alpha1"
|
||||
ocmworkv1 "open-cluster-management.io/api/work/v1"
|
||||
|
||||
v12 "k8s.io/api/core/v1"
|
||||
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
kruisev1alpha1 "github.com/openkruise/rollouts/api/v1alpha1"
|
||||
|
||||
coreoam "github.com/oam-dev/kubevela/apis/core.oam.dev"
|
||||
"github.com/oam-dev/kubevela/pkg/cue/packages"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/discoverymapper"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
var cfg *rest.Config
|
||||
var scheme *runtime.Scheme
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
var dm discoverymapper.DiscoveryMapper
|
||||
var pd *packages.PackageDiscover
|
||||
var testns string
|
||||
var dc *discovery.DiscoveryClient
|
||||
|
||||
func TestAddon(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecsWithDefaultAndCustomReporters(t,
|
||||
"Kruise rollout Suite test",
|
||||
[]Reporter{printer.NewlineReporter{}})
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
logf.SetLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter)))
|
||||
By("bootstrapping test environment")
|
||||
useExistCluster := false
|
||||
testEnv = &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "charts", "vela-core", "crds"), filepath.Join("", "testdata")},
|
||||
UseExistingCluster: &useExistCluster,
|
||||
}
|
||||
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
scheme = runtime.NewScheme()
|
||||
Expect(coreoam.AddToScheme(scheme)).NotTo(HaveOccurred())
|
||||
Expect(clientgoscheme.AddToScheme(scheme)).NotTo(HaveOccurred())
|
||||
Expect(crdv1.AddToScheme(scheme)).NotTo(HaveOccurred())
|
||||
_ = ocmclusterv1alpha1.Install(scheme)
|
||||
_ = ocmclusterv1.Install(scheme)
|
||||
_ = ocmworkv1.Install(scheme)
|
||||
_ = kruisev1alpha1.AddToScheme(scheme)
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
dc, err = discovery.NewDiscoveryClientForConfig(cfg)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(dc).ShouldNot(BeNil())
|
||||
|
||||
dm, err = discoverymapper.New(cfg)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(dm).ToNot(BeNil())
|
||||
pd, err = packages.NewPackageDiscover(cfg)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(pd).ToNot(BeNil())
|
||||
testns = "vela-system"
|
||||
Expect(k8sClient.Create(context.Background(),
|
||||
&v12.Namespace{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Namespace"}, ObjectMeta: metav1.ObjectMeta{
|
||||
Name: testns,
|
||||
}}))
|
||||
|
||||
close(done)
|
||||
}, 120)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.7.0
|
||||
creationTimestamp: null
|
||||
name: rollouts.rollouts.kruise.io
|
||||
spec:
|
||||
group: rollouts.kruise.io
|
||||
names:
|
||||
kind: Rollout
|
||||
listKind: RolloutList
|
||||
plural: rollouts
|
||||
singular: rollout
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- description: The rollout status phase
|
||||
jsonPath: .status.phase
|
||||
name: STATUS
|
||||
type: string
|
||||
- description: The rollout canary status step
|
||||
jsonPath: .status.canaryStatus.currentStepIndex
|
||||
name: CANARY_STEP
|
||||
type: integer
|
||||
- description: The rollout canary status step state
|
||||
jsonPath: .status.canaryStatus.currentStepState
|
||||
name: CANARY_STATE
|
||||
type: string
|
||||
- description: The rollout canary status message
|
||||
jsonPath: .status.message
|
||||
name: MESSAGE
|
||||
type: string
|
||||
- jsonPath: .metadata.creationTimestamp
|
||||
name: AGE
|
||||
type: date
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Rollout is the Schema for the rollouts API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: RolloutSpec defines the desired state of Rollout
|
||||
properties:
|
||||
objectRef:
|
||||
description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||
Important: Run "make" to regenerate code after modifying this file
|
||||
ObjectRef indicates workload'
|
||||
properties:
|
||||
workloadRef:
|
||||
description: WorkloadRef contains enough information to let you
|
||||
identify a workload for Rollout Batch release of the bypass
|
||||
properties:
|
||||
apiVersion:
|
||||
description: API Version of the referent
|
||||
type: string
|
||||
kind:
|
||||
description: Kind of the referent
|
||||
type: string
|
||||
name:
|
||||
description: Name of the referent
|
||||
type: string
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
strategy:
|
||||
description: rollout strategy
|
||||
properties:
|
||||
canary:
|
||||
description: CanaryStrategy defines parameters for a Replica Based
|
||||
Canary
|
||||
properties:
|
||||
steps:
|
||||
description: Steps define the order of phases to execute release
|
||||
in batches(20%, 40%, 60%, 80%, 100%)
|
||||
items:
|
||||
description: CanaryStep defines a step of a canary workload.
|
||||
properties:
|
||||
pause:
|
||||
description: Pause defines a pause stage for a rollout,
|
||||
manual or auto
|
||||
properties:
|
||||
duration:
|
||||
description: Duration the amount of time to wait
|
||||
before moving to the next step.
|
||||
format: int32
|
||||
type: integer
|
||||
type: object
|
||||
replicas:
|
||||
anyOf:
|
||||
- type: integer
|
||||
- type: string
|
||||
description: 'Replicas is the number of expected canary
|
||||
pods in this batch it can be an absolute number (ex:
|
||||
5) or a percentage of total pods.'
|
||||
x-kubernetes-int-or-string: true
|
||||
weight:
|
||||
description: SetWeight sets what percentage of the canary
|
||||
pods should receive
|
||||
format: int32
|
||||
type: integer
|
||||
type: object
|
||||
type: array
|
||||
trafficRoutings:
|
||||
description: TrafficRoutings hosts all the supported service
|
||||
meshes supported to enable more fine-grained traffic routing
|
||||
todo current only support one
|
||||
items:
|
||||
description: TrafficRouting hosts all the different configuration
|
||||
for supported service meshes to enable more fine-grained
|
||||
traffic routing
|
||||
properties:
|
||||
gracePeriodSeconds:
|
||||
description: Optional duration in seconds the traffic
|
||||
provider(e.g. nginx ingress controller) consumes the
|
||||
service, ingress configuration changes gracefully.
|
||||
format: int32
|
||||
type: integer
|
||||
ingress:
|
||||
description: Ingress holds Ingress specific configuration
|
||||
to route traffic, e.g. Nginx, Alb.
|
||||
properties:
|
||||
name:
|
||||
description: Name refers to the name of an `Ingress`
|
||||
resource in the same namespace as the `Rollout`
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
service:
|
||||
description: Service holds the name of a service which
|
||||
selects pods with stable version and don't select
|
||||
any pods with canary version.
|
||||
type: string
|
||||
type:
|
||||
description: nginx, alb, istio etc.
|
||||
type: string
|
||||
required:
|
||||
- service
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
paused:
|
||||
description: Paused indicates that the Rollout is paused. Default
|
||||
value is false
|
||||
type: boolean
|
||||
type: object
|
||||
required:
|
||||
- objectRef
|
||||
- strategy
|
||||
type: object
|
||||
status:
|
||||
description: RolloutStatus defines the observed state of Rollout
|
||||
properties:
|
||||
canaryStatus:
|
||||
description: Canary describes the state of the canary rollout
|
||||
properties:
|
||||
canaryReadyReplicas:
|
||||
description: CanaryReadyReplicas the numbers of ready canary revision
|
||||
pods
|
||||
format: int32
|
||||
type: integer
|
||||
canaryReplicas:
|
||||
description: CanaryReplicas the numbers of canary revision pods
|
||||
format: int32
|
||||
type: integer
|
||||
canaryRevision:
|
||||
description: CanaryRevision is calculated by rollout based on
|
||||
podTemplateHash, and the internal logic flow uses It may be
|
||||
different from rs podTemplateHash in different k8s versions,
|
||||
so it cannot be used as service selector label
|
||||
type: string
|
||||
canaryService:
|
||||
description: CanaryService holds the name of a service which selects
|
||||
pods with canary version and don't select any pods with stable
|
||||
version.
|
||||
type: string
|
||||
currentStepIndex:
|
||||
description: CurrentStepIndex defines the current step of the
|
||||
rollout is on. If the current step index is null, the controller
|
||||
will execute the rollout.
|
||||
format: int32
|
||||
type: integer
|
||||
currentStepState:
|
||||
type: string
|
||||
lastReadyTime:
|
||||
description: The last time this step pods is ready.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
observedWorkloadGeneration:
|
||||
description: observedWorkloadGeneration is the most recent generation
|
||||
observed for this Rollout ref workload generation.
|
||||
format: int64
|
||||
type: integer
|
||||
podTemplateHash:
|
||||
description: pod template hash is used as service selector label
|
||||
type: string
|
||||
rolloutHash:
|
||||
description: RolloutHash from rollout.spec object
|
||||
type: string
|
||||
required:
|
||||
- canaryReadyReplicas
|
||||
- canaryReplicas
|
||||
- canaryService
|
||||
- currentStepState
|
||||
- podTemplateHash
|
||||
type: object
|
||||
conditions:
|
||||
description: Conditions a list of conditions a rollout can have.
|
||||
items:
|
||||
description: RolloutCondition describes the state of a rollout at
|
||||
a certain point.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: Last time the condition transitioned from one status
|
||||
to another.
|
||||
format: date-time
|
||||
type: string
|
||||
lastUpdateTime:
|
||||
description: The last time this condition was updated.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: A human readable message indicating details about
|
||||
the transition.
|
||||
type: string
|
||||
reason:
|
||||
description: The reason for the condition's last transition.
|
||||
type: string
|
||||
status:
|
||||
description: Phase of the condition, one of True, False, Unknown.
|
||||
type: string
|
||||
type:
|
||||
description: Type of rollout condition.
|
||||
type: string
|
||||
required:
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
message:
|
||||
description: Message provides details on why the rollout is in its
|
||||
current phase
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration is the most recent generation observed
|
||||
for this Rollout.
|
||||
format: int64
|
||||
type: integer
|
||||
phase:
|
||||
description: BlueGreenStatus *BlueGreenStatus `json:"blueGreenStatus,omitempty"`
|
||||
Phase is the rollout phase.
|
||||
type: string
|
||||
stableRevision:
|
||||
description: CanaryRevision the hash of the canary pod template CanaryRevision
|
||||
string `json:"canaryRevision,omitempty"` StableRevision indicates
|
||||
the revision pods that has successfully rolled out
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
||||
Reference in New Issue
Block a user