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:
wyike 2022-06-24 18:03:04 +08:00 committed by GitHub
parent dc660fc97d
commit c4e1f39d28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1820 additions and 253 deletions

20
go.mod
View File

@ -105,6 +105,18 @@ require (
sigs.k8s.io/yaml v1.3.0
)
require (
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/openkruise/rollouts v0.1.1-0.20220622054609-149e5a48da5e
github.com/xanzy/ssh-agent v0.3.0 // indirect
golang.org/x/net v0.0.0-20220516155154-20f960328961 // indirect
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
google.golang.org/protobuf v1.28.0 // indirect
)
require (
cloud.google.com/go/compute v1.6.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
@ -148,7 +160,6 @@ require (
github.com/creack/pty v1.1.11 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/docker/cli v20.10.16+incompatible // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/docker v20.10.16+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/docker/go-connections v0.4.0 // indirect
@ -190,8 +201,6 @@ require (
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
@ -201,7 +210,6 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/klauspost/compress v1.15.4 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/kr/pty v1.1.8 // indirect
@ -254,7 +262,6 @@ require (
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tjfoc/gmsm v1.3.2 // indirect
github.com/xanzy/ssh-agent v0.3.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.0.2 // indirect
github.com/xdg-go/stringprep v1.0.2 // indirect
@ -281,16 +288,13 @@ require (
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/net v0.0.0-20220516155154-20f960328961 // indirect
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3 // indirect
google.golang.org/grpc v1.45.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/gorp.v1 v1.7.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.63.2 // indirect

33
go.sum
View File

@ -1608,6 +1608,8 @@ github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvw
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY=
@ -1627,6 +1629,7 @@ github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDs
github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg=
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
@ -1662,8 +1665,11 @@ github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqi
github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
github.com/openkruise/kruise-api v1.0.0/go.mod h1:kxV/UA/vrf/hz3z+kL21c0NOawC6K1ZjaKcJFgiOwsE=
github.com/openkruise/kruise-api v1.1.0 h1:ZRhV0FnxUp4XHc60YPkUqj2LJD4GRFB92qhtdgU6Zhc=
github.com/openkruise/kruise-api v1.1.0/go.mod h1:kxV/UA/vrf/hz3z+kL21c0NOawC6K1ZjaKcJFgiOwsE=
github.com/openkruise/rollouts v0.1.1-0.20220622054609-149e5a48da5e h1:jUMEDsA0OOpp0262pK8MV8M2glac+jIjx+q5Aydn6G0=
github.com/openkruise/rollouts v0.1.1-0.20220622054609-149e5a48da5e/go.mod h1:SORsT96ssCqMJYSVA90v6Z52utlV2jxPlyGh4czRfHA=
github.com/openshift/api v0.0.0-20210915110300-3cd8091317c4/go.mod h1:RsQCVJu4qhUawxxDP7pGlwU3IA4F01wYm3qKEu29Su8=
github.com/openshift/api v0.0.0-20211209135129-c58d9f695577/go.mod h1:DoslCwtqUpr3d/gsbq4ZlkaMEdYqKxuypsDjorcHhME=
github.com/openshift/build-machinery-go v0.0.0-20210115170933-e575b44a7a94/go.mod h1:b1BuldmJlbA/xYtdZvKi+7j5YGB44qJUJDZ9zwiNCfE=
@ -2199,6 +2205,7 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
golang.org/x/arch v0.0.0-20180920145803-b19384d3c130/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8=
@ -2375,6 +2382,7 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211215060638-4ddde0e984e9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
@ -2547,6 +2555,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -3073,7 +3082,10 @@ k8s.io/api v0.21.1/go.mod h1:FstGROTmsSHBarKc8bylzXih8BLNYTiS3TZcsoEDg2s=
k8s.io/api v0.21.2/go.mod h1:Lv6UGJZ1rlMI1qusN8ruAp9PUBFyBwpEHAdG24vIsiU=
k8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg=
k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY=
k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8=
k8s.io/api v0.22.4/go.mod h1:Rgs+9gIGYC5laXQSZZ9JqT5NevNgoGiOdVWi1BAB3qk=
k8s.io/api v0.22.4/go.mod h1:Rgs+9gIGYC5laXQSZZ9JqT5NevNgoGiOdVWi1BAB3qk=
k8s.io/api v0.22.6/go.mod h1:q1F7IfaNrbi/83ebLy3YFQYLjPSNyunZ/IXQxMmbwCg=
k8s.io/api v0.23.0/go.mod h1:8wmDdLBHBNxtOIytwLstXt5E9PddnZb0GaMcqsvDBpg=
k8s.io/api v0.23.1/go.mod h1:WfXnOnwSqNtG62Y1CdjoMxh7r7u9QXGCkA1u0na2jgo=
k8s.io/api v0.23.5/go.mod h1:Na4XuKng8PXJ2JsploYYrivXrINeTaycCGcYgF91Xm8=
@ -3089,7 +3101,9 @@ k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKi
k8s.io/apiextensions-apiserver v0.21.2/go.mod h1:+Axoz5/l3AYpGLlhJDfcVQzCerVYq3K3CvDMvw6X1RA=
k8s.io/apiextensions-apiserver v0.21.3/go.mod h1:kl6dap3Gd45+21Jnh6utCx8Z2xxLm8LGDkprcd+KbsE=
k8s.io/apiextensions-apiserver v0.22.1/go.mod h1:HeGmorjtRmRLE+Q8dJu6AYRoZccvCMsghwS8XTUYb2c=
k8s.io/apiextensions-apiserver v0.22.2/go.mod h1:2E0Ve/isxNl7tWLSUDgi6+cmwHi5fQRdwGVCxbC+KFA=
k8s.io/apiextensions-apiserver v0.22.4/go.mod h1:kH9lxD8dbJ+k0ZizGET55lFgdGjO8t45fgZnCVdZEpw=
k8s.io/apiextensions-apiserver v0.22.6/go.mod h1:wNsLwy8mfIkGThiv4Qq/Hy4qRazViKXqmH5pfYiRKyY=
k8s.io/apiextensions-apiserver v0.23.0/go.mod h1:xIFAEEDlAZgpVBl/1VSjGDmLoXAWRG40+GsWhKhAxY4=
k8s.io/apiextensions-apiserver v0.23.5/go.mod h1:ntcPWNXS8ZPKN+zTXuzYMeg731CP0heCTl6gYBxLcuQ=
k8s.io/apiextensions-apiserver v0.23.6 h1:v58cQ6Z0/GK1IXYr+oW0fnYl52o9LTY0WgoWvI8uv5Q=
@ -3119,7 +3133,9 @@ k8s.io/apimachinery v0.21.1/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswP
k8s.io/apimachinery v0.21.2/go.mod h1:CdTY8fU/BlvAbJ2z/8kBwimGki5Zp8/fbVuLY8gJumM=
k8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI=
k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
k8s.io/apimachinery v0.22.4/go.mod h1:yU6oA6Gnax9RrxGzVvPFFJ+mpnW6PBSqp0sx0I0HHW0=
k8s.io/apimachinery v0.22.6/go.mod h1:ZvVLP5iLhwVFg2Yx9Gh5W0um0DUauExbRhe+2Z8I1EU=
k8s.io/apimachinery v0.23.0/go.mod h1:fFCTTBKvKcwTPFzjlcxp91uPFZr+JA0FubU4fLzzFYc=
k8s.io/apimachinery v0.23.1/go.mod h1:SADt2Kl8/sttJ62RRsi9MIV4o8f5S3coArm0Iu3fBno=
k8s.io/apimachinery v0.23.5/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM=
@ -3139,7 +3155,10 @@ k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q=
k8s.io/apiserver v0.21.2/go.mod h1:lN4yBoGyiNT7SC1dmNk0ue6a5Wi6O3SWOIw91TsucQw=
k8s.io/apiserver v0.21.3/go.mod h1:eDPWlZG6/cCCMj/JBcEpDoK+I+6i3r9GsChYBHSbAzU=
k8s.io/apiserver v0.22.1/go.mod h1:2mcM6dzSt+XndzVQJX21Gx0/Klo7Aen7i0Ai6tIa400=
k8s.io/apiserver v0.22.2/go.mod h1:vrpMmbyjWrgdyOvZTSpsusQq5iigKNWv9o9KlDAbBHI=
k8s.io/apiserver v0.22.4/go.mod h1:38WmcUZiiy41A7Aty8/VorWRa8vDGqoUzDf2XYlku0E=
k8s.io/apiserver v0.22.4/go.mod h1:38WmcUZiiy41A7Aty8/VorWRa8vDGqoUzDf2XYlku0E=
k8s.io/apiserver v0.22.6/go.mod h1:OlL1rGa2kKWGj2JEXnwBcul/BwC9Twe95gm4ohtiIIs=
k8s.io/apiserver v0.23.0/go.mod h1:Cec35u/9zAepDPPFyT+UMrgqOCjgJ5qtfVJDxjZYmt4=
k8s.io/apiserver v0.23.1/go.mod h1:Bqt0gWbeM2NefS8CjWswwd2VNAKN6lUKR85Ft4gippY=
k8s.io/apiserver v0.23.5/go.mod h1:7wvMtGJ42VRxzgVI7jkbKvMbuCbVbgsWFT7RyXiRNTw=
@ -3173,7 +3192,9 @@ k8s.io/client-go v0.21.1/go.mod h1:/kEw4RgW+3xnBGzvp9IWxKSNA+lXn3A7AuH3gdOAzLs=
k8s.io/client-go v0.21.2/go.mod h1:HdJ9iknWpbl3vMGtib6T2PyI/VYxiZfq936WNVHBRrA=
k8s.io/client-go v0.21.3/go.mod h1:+VPhCgTsaFmGILxR/7E1N0S+ryO010QBeNCv5JwRGYU=
k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk=
k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U=
k8s.io/client-go v0.22.4/go.mod h1:Yzw4e5e7h1LNHA4uqnMVrpEpUs1hJOiuBsJKIlRCHDA=
k8s.io/client-go v0.22.6/go.mod h1:TffU4AV2idZGeP+g3kdFZP+oHVHWPL1JYFySOALriw0=
k8s.io/client-go v0.23.0/go.mod h1:hrDnpnK1mSr65lHHcUuIZIXDgEbzc7/683c6hyG4jTA=
k8s.io/client-go v0.23.1/go.mod h1:6QSI8fEuqD4zgFK0xbdwfB/PthBsIxCJMa3s17WlcO0=
k8s.io/client-go v0.23.5/go.mod h1:flkeinTO1CirYgzMPRWxUCnV0G4Fbu2vLhYCObnt/r4=
@ -3189,10 +3210,13 @@ k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRV
k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c=
k8s.io/code-generator v0.20.0/go.mod h1:UsqdF+VX4PU2g46NC2JRs4gc+IfrctnwHb76RNbWHJg=
k8s.io/code-generator v0.20.10/go.mod h1:i6FmG+QxaLxvJsezvZp0q/gAEzzOz3U53KFibghWToU=
k8s.io/code-generator v0.20.10/go.mod h1:i6FmG+QxaLxvJsezvZp0q/gAEzzOz3U53KFibghWToU=
k8s.io/code-generator v0.21.2/go.mod h1:8mXJDCB7HcRo1xiEQstcguZkbxZaqeUOrO9SsicWs3U=
k8s.io/code-generator v0.21.3/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo=
k8s.io/code-generator v0.22.1/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
k8s.io/code-generator v0.22.2/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
k8s.io/code-generator v0.22.4/go.mod h1:qjYl54pQ/emhkT0UxbufbREYJMWsHNNV/jSVwhYZQGw=
k8s.io/code-generator v0.22.6/go.mod h1:iOZwYADSgFPNGWfqHFfg1V0TNJnl1t0WyZluQp4baqU=
k8s.io/code-generator v0.23.0/go.mod h1:vQvOhDXhuzqiVfM/YHp+dmg10WDZCchJVObc9MvowsE=
k8s.io/code-generator v0.23.1/go.mod h1:V7yn6VNTCWW8GqodYCESVo95fuiEg713S8B7WacWZDA=
k8s.io/code-generator v0.23.5/go.mod h1:S0Q1JVA+kSzTI1oUvbKAxZY/DYbA/ZUb4Uknog12ETk=
@ -3213,7 +3237,9 @@ k8s.io/component-base v0.20.10/go.mod h1:ZKOEin1xu68aJzxgzl5DZSp5J1IrjAOPlPN90/t
k8s.io/component-base v0.21.2/go.mod h1:9lvmIThzdlrJj5Hp8Z/TOgIkdfsNARQ1pT+3PByuiuc=
k8s.io/component-base v0.21.3/go.mod h1:kkuhtfEHeZM6LkX0saqSK8PbdO7A0HigUngmhhrwfGQ=
k8s.io/component-base v0.22.1/go.mod h1:0D+Bl8rrnsPN9v0dyYvkqFfBeAd4u7n77ze+p8CMiPo=
k8s.io/component-base v0.22.2/go.mod h1:5Br2QhI9OTe79p+TzPe9JKNQYvEKbq9rTJDWllunGug=
k8s.io/component-base v0.22.4/go.mod h1:MrSaQy4a3tFVViff8TZL6JHYSewNCLshZCwHYM58v5A=
k8s.io/component-base v0.22.6/go.mod h1:ngHLefY4J5fq2fApNdbWyj4yh0lvw36do4aAjNN8rc8=
k8s.io/component-base v0.23.0/go.mod h1:DHH5uiFvLC1edCpvcTDV++NKULdYYU6pR9Tt3HIKMKI=
k8s.io/component-base v0.23.1/go.mod h1:6llmap8QtJIXGDd4uIWJhAq0Op8AtQo6bDW2RrNMTeo=
k8s.io/component-base v0.23.5/go.mod h1:c5Nq44KZyt1aLl0IpHX82fhsn84Sb0jjzwjpcA42bY0=
@ -3262,6 +3288,7 @@ k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAG
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4=
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk=
k8s.io/kubectl v0.0.0-20191219154910-1528d4eea6dd/go.mod h1:9ehGcuUGjXVZh0qbYSB0vvofQw2JQe6c6cO0k4wu/Oo=
@ -3287,6 +3314,7 @@ k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/
k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20211208161948-7d6a63dca704/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
@ -3330,6 +3358,7 @@ sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2
sigs.k8s.io/controller-runtime v0.6.2/go.mod h1:vhcq/rlnENJ09SIRp3EveTaZ0yqH526hjf9iJdbUJ/E=
sigs.k8s.io/controller-runtime v0.9.2/go.mod h1:TxzMCHyEUpaeuOiZx/bIdc2T81vfs/aKdvJt9wuu0zk=
sigs.k8s.io/controller-runtime v0.9.5/go.mod h1:q6PpkM5vqQubEKUKOM6qr06oXGzOBcCby1DA9FbyZeA=
sigs.k8s.io/controller-runtime v0.10.3/go.mod h1:CQp8eyUQZ/Q7PJvnIrB6/hgfTC1kBkGylwsLgOQi1WY=
sigs.k8s.io/controller-runtime v0.11.0/go.mod h1:KKwLiTooNGu+JmLZGn9Sl3Gjmfj66eMbCQznLP5zcqA=
sigs.k8s.io/controller-runtime v0.11.1/go.mod h1:KKwLiTooNGu+JmLZGn9Sl3Gjmfj66eMbCQznLP5zcqA=
sigs.k8s.io/controller-runtime v0.11.2 h1:H5GTxQl0Mc9UjRJhORusqfJCIjBO8UtUxGggCwL1rLA=
@ -3370,7 +3399,11 @@ sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK
sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
sigs.k8s.io/structured-merge-diff/v4 v4.2.0/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y=
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y=
sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w=
sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@ -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))

200
pkg/rollout/rollout.go Normal file
View File

@ -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
}

155
pkg/rollout/rollout_test.go Normal file
View File

@ -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,
},
},
}

119
pkg/rollout/suit_test.go Normal file
View File

@ -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())
})

290
pkg/rollout/testdata/rollouts.yaml vendored Normal file
View File

@ -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: []

View File

@ -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
}

View File

@ -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)

View File

@ -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))
})

View File

@ -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
}

View File

@ -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,
},
},
}

View File

@ -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())
})

View File

@ -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: []

Binary file not shown.

View File

@ -0,0 +1,8 @@
apiVersion: v2
appVersion: 1.0.1
description: This is a test sample addon
home: https://terraform.io/
icon: https://www.terraform.io/assets/images/logo-text-8c3ba8a6.svg
name: sample
type: library
version: 1.0.1

View File

@ -20,23 +20,16 @@ import (
"context"
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
k8stypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
oamcommon "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/apis/types"
"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/multicluster"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/resourcetracker"
"github.com/oam-dev/kubevela/pkg/utils/common"
velaerrors "github.com/oam-dev/kubevela/pkg/utils/errors"
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
"github.com/oam-dev/kubevela/pkg/workflow/operation"
"github.com/oam-dev/kubevela/references/appfile"
)
@ -79,18 +72,19 @@ func NewWorkflowSuspendCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra
if err != nil {
return err
}
if app.Status.Workflow == nil {
return fmt.Errorf("the workflow in application is not running")
}
client, err := c.GetClient()
config, err := c.GetConfig()
if err != nil {
return err
}
err = suspendWorkflow(client, app)
config.Wrap(multicluster.NewSecretModeMultiClusterRoundTripper)
cli, err := c.GetClient()
if err != nil {
return err
}
return nil
wo := operation.NewWorkflowOperator(cli, cmd.OutOrStdout())
return wo.Suspend(context.Background(), app)
},
}
addNamespaceAndEnvArg(cmd)
@ -116,29 +110,19 @@ func NewWorkflowResumeCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.
if err != nil {
return err
}
if app.Status.Workflow == nil {
return fmt.Errorf("the workflow in application is not running")
config, err := c.GetConfig()
if err != nil {
return err
}
if app.Status.Workflow.Terminated {
return fmt.Errorf("can not resume a terminated workflow")
}
if !app.Status.Workflow.Suspend {
_, err := ioStream.Out.Write([]byte("the workflow is not suspending\n"))
if err != nil {
return err
}
return nil
}
client, err := c.GetClient()
config.Wrap(multicluster.NewSecretModeMultiClusterRoundTripper)
cli, err := c.GetClient()
if err != nil {
return err
}
err = resumeWorkflow(client, app)
if err != nil {
return err
}
return nil
wo := operation.NewWorkflowOperator(cli, cmd.OutOrStdout())
return wo.Resume(context.Background(), app)
},
}
addNamespaceAndEnvArg(cmd)
@ -167,15 +151,12 @@ func NewWorkflowTerminateCommand(c common.Args, ioStream cmdutil.IOStreams) *cob
if app.Status.Workflow == nil {
return fmt.Errorf("the workflow in application is not running")
}
client, err := c.GetClient()
cli, err := c.GetClient()
if err != nil {
return err
}
err = terminateWorkflow(client, app)
if err != nil {
return err
}
return nil
wo := operation.NewWorkflowOperator(cli, cmd.OutOrStdout())
return wo.Terminate(context.Background(), app)
},
}
addNamespaceAndEnvArg(cmd)
@ -201,19 +182,18 @@ func NewWorkflowRestartCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra
if err != nil {
return err
}
if app.Status.Workflow == nil {
return fmt.Errorf("the workflow in application is not running")
config, err := c.GetConfig()
if err != nil {
return err
}
client, err := c.GetClient()
config.Wrap(multicluster.NewSecretModeMultiClusterRoundTripper)
cli, err := c.GetClient()
if err != nil {
return err
}
err = restartWorkflow(client, app)
if err != nil {
return err
}
return nil
wo := operation.NewWorkflowOperator(cli, cmd.OutOrStdout())
return wo.Restart(context.Background(), app)
},
}
addNamespaceAndEnvArg(cmd)
@ -239,205 +219,40 @@ func NewWorkflowRollbackCommand(c common.Args, ioStream cmdutil.IOStreams) *cobr
if err != nil {
return err
}
config, err := c.GetConfig()
if err != nil {
return err
}
config.Wrap(multicluster.NewSecretModeMultiClusterRoundTripper)
cli, err := c.GetClient()
if err != nil {
return err
}
if app.Status.Workflow != nil && !app.Status.Workflow.Terminated && !app.Status.Workflow.Suspend && !app.Status.Workflow.Finished {
return fmt.Errorf("can not rollback a running workflow")
}
client, err := c.GetClient()
if err != nil {
return err
}
if oam.GetPublishVersion(app) == "" {
if app.Status.LatestRevision == nil || app.Status.LatestRevision.Name == "" {
return fmt.Errorf("the latest revision is not set: %s", app.Name)
}
// get the last revision
revision := &v1beta1.ApplicationRevision{}
if err := cli.Get(context.TODO(), k8stypes.NamespacedName{Name: app.Status.LatestRevision.Name, Namespace: app.Namespace}, revision); err != nil {
return fmt.Errorf("failed to get the latest revision: %w", err)
}
err = rollbackWorkflow(cmd, client, app)
if err != nil {
return err
app.Spec = revision.Spec.Application.Spec
if err := cli.Status().Update(context.TODO(), app); err != nil {
return err
}
fmt.Printf("Successfully rollback workflow to the latest revision: %s\n", app.Name)
return nil
}
return nil
wo := operation.NewWorkflowOperator(cli, cmd.OutOrStdout())
return wo.Rollback(context.Background(), app)
},
}
addNamespaceAndEnvArg(cmd)
return cmd
}
func suspendWorkflow(kubecli client.Client, app *v1beta1.Application) error {
appKey := client.ObjectKeyFromObject(app)
ctx := context.Background()
if err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
if err := kubecli.Get(ctx, appKey, app); err != nil {
return err
}
// set the workflow suspend to true
app.Status.Workflow.Suspend = true
return kubecli.Status().Patch(ctx, app, client.Merge)
}); err != nil {
return err
}
fmt.Printf("Successfully suspend workflow: %s\n", app.Name)
return nil
}
func resumeWorkflow(kubecli client.Client, app *v1beta1.Application) error {
if err := service.ResumeWorkflow(context.TODO(), kubecli, app); err != nil {
return err
}
fmt.Printf("Successfully resume workflow: %s\n", app.Name)
return nil
}
func terminateWorkflow(kubecli client.Client, app *v1beta1.Application) error {
if err := service.TerminateWorkflow(context.TODO(), kubecli, app); err != nil {
return err
}
fmt.Printf("Successfully terminate workflow: %s\n", app.Name)
return nil
}
func restartWorkflow(kubecli client.Client, app *v1beta1.Application) error {
// reset the workflow status to restart the workflow
app.Status.Workflow = nil
if err := kubecli.Status().Update(context.TODO(), app); err != nil {
return err
}
fmt.Printf("Successfully restart workflow: %s\n", app.Name)
return nil
}
func rollbackWorkflow(cmd *cobra.Command, kubecli client.Client, app *v1beta1.Application) error {
if oam.GetPublishVersion(app) != "" {
return rollbackApplicationWithPublishVersion(cmd, kubecli, app)
}
if app.Status.LatestRevision == nil || app.Status.LatestRevision.Name == "" {
return fmt.Errorf("the latest revision is not set: %s", app.Name)
}
// get the last revision
revision := &v1beta1.ApplicationRevision{}
if err := kubecli.Get(context.TODO(), k8stypes.NamespacedName{Name: app.Status.LatestRevision.Name, Namespace: app.Namespace}, revision); err != nil {
return fmt.Errorf("failed to get the latest revision: %w", err)
}
app.Spec = revision.Spec.Application.Spec
if err := kubecli.Status().Update(context.TODO(), app); err != nil {
return err
}
fmt.Printf("Successfully rollback workflow to the latest revision: %s\n", app.Name)
return nil
}
func rollbackApplicationWithPublishVersion(cmd *cobra.Command, cli client.Client, app *v1beta1.Application) error {
ctx := context.Background()
appRevs, err := application.GetSortedAppRevisions(ctx, 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, 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)
}
cmd.Printf("Find succeeded application revision %s (PublishVersion: %s) to rollback.\n", rev.Name, publishVersion)
appKey := client.ObjectKeyFromObject(app)
// rollback application spec and freeze
controllerRequirement, err := utils.FreezeApplication(ctx, 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)
}
cmd.Printf("Application spec rollback successfully.\n")
// rollback application status
if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
if err = cli.Get(ctx, appKey, app); err != nil {
return err
}
app.Status.Workflow = rev.Status.Workflow
app.Status.Services = []oamcommon.ApplicationComponentStatus{}
app.Status.AppliedResources = []oamcommon.ClusterObjectReference{}
for _, rsc := range matchRT.Spec.ManagedResources {
app.Status.AppliedResources = append(app.Status.AppliedResources, rsc.ClusterObjectReference)
}
app.Status.LatestRevision = &oamcommon.Revision{
Name: rev.Name,
Revision: int64(revisionNumber),
RevisionHash: rev.GetLabels()[oam.LabelAppRevisionHash],
}
return cli.Status().Update(ctx, app)
}); err != nil {
return errors.Wrapf(err, "failed to rollback application status to revision %s (PublishVersion: %s)", rev.Name, publishVersion)
}
cmd.Printf("Application status rollback successfully.\n")
// update resource tracker generation
matchRTKey := client.ObjectKeyFromObject(matchRT)
if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error {
if err = cli.Get(ctx, matchRTKey, matchRT); err != nil {
return err
}
matchRT.Spec.ApplicationGeneration = app.Generation
return 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, cli, app, nil, controllerRequirement); err != nil {
return errors.Wrapf(err, "failed to resume application to restart")
}
cmd.Printf("Application rollback completed.\n")
// clean up outdated revisions
var errs velaerrors.ErrorList
for _, _rev := range outdatedRev {
if err = cli.Delete(ctx, _rev); err != nil {
errs = append(errs, err)
}
}
if errs.HasError() {
return errors.Wrapf(errs, "failed to clean up outdated revisions")
}
cmd.Printf("Application outdated revision cleaned up.\n")
return nil
}

View File

@ -225,6 +225,8 @@ func TestWorkflowResume(t *testing.T) {
r := require.New(t)
cmd := NewWorkflowResumeCommand(c, ioStream)
initCommand(cmd)
// clean up the arguments before start
cmd.SetArgs([]string{})
client, err := c.GetClient()
r.NoError(err)
if tc.app != nil {
@ -352,6 +354,8 @@ func TestWorkflowTerminate(t *testing.T) {
r := require.New(t)
cmd := NewWorkflowTerminateCommand(c, ioStream)
initCommand(cmd)
// clean up the arguments before start
cmd.SetArgs([]string{})
client, err := c.GetClient()
r.NoError(err)
if tc.app != nil {
@ -444,6 +448,8 @@ func TestWorkflowRestart(t *testing.T) {
r := require.New(t)
cmd := NewWorkflowRestartCommand(c, ioStream)
initCommand(cmd)
// clean up the arguments before start
cmd.SetArgs([]string{})
client, err := c.GetClient()
r.NoError(err)
if tc.app != nil {
@ -567,6 +573,8 @@ func TestWorkflowRollback(t *testing.T) {
r := require.New(t)
cmd := NewWorkflowRollbackCommand(c, ioStream)
initCommand(cmd)
// clean up the arguments before start
cmd.SetArgs([]string{})
client, err := c.GetClient()
r.NoError(err)
if tc.app != nil {