diff --git a/go.mod b/go.mod index f0fd72de..d1f79a84 100644 --- a/go.mod +++ b/go.mod @@ -6,19 +6,21 @@ require ( bou.ke/monkey v1.0.2 github.com/blang/semver v3.5.1+incompatible github.com/go-ini/ini v1.62.0 + github.com/go-logr/logr v0.4.0 github.com/go-sql-driver/mysql v1.6.0 github.com/go-test/deep v1.0.7 + github.com/golang/glog v1.0.0 github.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365 github.com/imdario/mergo v0.3.12 - github.com/onsi/ginkgo v1.16.4 - github.com/onsi/gomega v1.13.0 + github.com/onsi/ginkgo v1.16.5 + github.com/onsi/gomega v1.17.0 github.com/presslabs/controller-util v0.3.0 - github.com/spf13/cobra v1.1.1 + github.com/spf13/cobra v1.1.3 github.com/stretchr/testify v1.7.0 - k8s.io/api v0.21.2 - k8s.io/apimachinery v0.21.2 - k8s.io/client-go v0.21.2 - k8s.io/component-base v0.21.2 + golang.org/x/tools v0.1.8-0.20211028023602-8de2a7fd1736 // indirect + k8s.io/api v0.21.3 + k8s.io/apimachinery v0.21.3 + k8s.io/client-go v0.21.3 k8s.io/klog/v2 v2.8.0 - sigs.k8s.io/controller-runtime v0.9.2 + sigs.k8s.io/controller-runtime v0.9.5 ) diff --git a/go.sum b/go.sum index 981ec73e..e74560b9 100644 --- a/go.sum +++ b/go.sum @@ -152,6 +152,8 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -322,14 +324,17 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= 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/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= 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.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= @@ -388,8 +393,9 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -416,6 +422,7 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= @@ -481,6 +488,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -512,8 +520,9 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -575,8 +584,9 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= @@ -587,15 +597,17 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 h1:Vv0JUPWTyeqUq42B2WJ1FeIDjjvGKoA2Ss+Ts0lAVbs= golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -637,8 +649,9 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.4 h1:cVngSRcfgyZCzys3KYOpCFa+4dqX/Oub9tAq00ttGVs= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8-0.20211028023602-8de2a7fd1736 h1:cw6nUxdoEN5iEIWYD8aAsTZ8iYjLVNiHAb7xz/80WO4= +golang.org/x/tools v0.1.8-0.20211028023602-8de2a7fd1736/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -743,18 +756,25 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.21.2 h1:vz7DqmRsXTCSa6pNxXwQ1IYeAZgdIsua+DZU+o+SX3Y= k8s.io/api v0.21.2/go.mod h1:Lv6UGJZ1rlMI1qusN8ruAp9PUBFyBwpEHAdG24vIsiU= -k8s.io/apiextensions-apiserver v0.21.2 h1:+exKMRep4pDrphEafRvpEi79wTnCFMqKf8LBtlA3yrE= +k8s.io/api v0.21.3 h1:cblWILbLO8ar+Fj6xdDGr603HRsf8Wu9E9rngJeprZQ= +k8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg= k8s.io/apiextensions-apiserver v0.21.2/go.mod h1:+Axoz5/l3AYpGLlhJDfcVQzCerVYq3K3CvDMvw6X1RA= -k8s.io/apimachinery v0.21.2 h1:vezUc/BHqWlQDnZ+XkrpXSmnANSLbpnlpwo0Lhk0gpc= +k8s.io/apiextensions-apiserver v0.21.3 h1:+B6biyUWpqt41kz5x6peIsljlsuwvNAp/oFax/j2/aY= +k8s.io/apiextensions-apiserver v0.21.3/go.mod h1:kl6dap3Gd45+21Jnh6utCx8Z2xxLm8LGDkprcd+KbsE= k8s.io/apimachinery v0.21.2/go.mod h1:CdTY8fU/BlvAbJ2z/8kBwimGki5Zp8/fbVuLY8gJumM= +k8s.io/apimachinery v0.21.3 h1:3Ju4nvjCngxxMYby0BimUk+pQHPOQp3eCGChk5kfVII= +k8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI= k8s.io/apiserver v0.21.2/go.mod h1:lN4yBoGyiNT7SC1dmNk0ue6a5Wi6O3SWOIw91TsucQw= -k8s.io/client-go v0.21.2 h1:Q1j4L/iMN4pTw6Y4DWppBoUxgKO8LbffEMVEV00MUp0= +k8s.io/apiserver v0.21.3/go.mod h1:eDPWlZG6/cCCMj/JBcEpDoK+I+6i3r9GsChYBHSbAzU= k8s.io/client-go v0.21.2/go.mod h1:HdJ9iknWpbl3vMGtib6T2PyI/VYxiZfq936WNVHBRrA= +k8s.io/client-go v0.21.3 h1:J9nxZTOmvkInRDCzcSNQmPJbDYN/PjlxXT9Mos3HcLg= +k8s.io/client-go v0.21.3/go.mod h1:+VPhCgTsaFmGILxR/7E1N0S+ryO010QBeNCv5JwRGYU= k8s.io/code-generator v0.21.2/go.mod h1:8mXJDCB7HcRo1xiEQstcguZkbxZaqeUOrO9SsicWs3U= -k8s.io/component-base v0.21.2 h1:EsnmFFoJ86cEywC0DoIkAUiEV6fjgauNugiw1lmIjs4= +k8s.io/code-generator v0.21.3/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo= k8s.io/component-base v0.21.2/go.mod h1:9lvmIThzdlrJj5Hp8Z/TOgIkdfsNARQ1pT+3PByuiuc= +k8s.io/component-base v0.21.3 h1:4WuuXY3Npa+iFfi2aDRiOz+anhNvRfye0859ZgfC5Og= +k8s.io/component-base v0.21.3/go.mod h1:kkuhtfEHeZM6LkX0saqSK8PbdO7A0HigUngmhhrwfGQ= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= @@ -764,17 +784,20 @@ k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7BrMc/KQBBT/Jyee8XtLf4x0= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210527160623-6fdb442a123b h1:MSqsVQ3pZvPGTqCjptfimO2WjG7A9un2zcpiHkA6M/s= k8s.io/utils v0.0.0-20210527160623-6fdb442a123b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471 h1:DnzUXII7sVg1FJ/4JX6YDRJfLNAC7idRatPwe07suiI= +k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.19/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/controller-runtime v0.9.2 h1:MnCAsopQno6+hI9SgJHKddzXpmv2wtouZz6931Eax+Q= sigs.k8s.io/controller-runtime v0.9.2/go.mod h1:TxzMCHyEUpaeuOiZx/bIdc2T81vfs/aKdvJt9wuu0zk= +sigs.k8s.io/controller-runtime v0.9.5 h1:WThcFE6cqctTn2jCZprLICO6BaKZfhsT37uAapTNfxc= +sigs.k8s.io/controller-runtime v0.9.5/go.mod h1:q6PpkM5vqQubEKUKOM6qr06oXGzOBcCby1DA9FbyZeA= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8= sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/test/e2e/README.md b/test/e2e/README.md new file mode 100644 index 00000000..16860d99 --- /dev/null +++ b/test/e2e/README.md @@ -0,0 +1,61 @@ +# How to run e2e test + +## Prerequisites + +Prepare a client connected to K8S. + +## Hands-on Lab + +### Step 1: Configure environment variables + +``` +export KUBECONFIG=$HOME/.kube/config +``` + +### Step 2: Run test + +``` +make e2e-local +``` +Example output of simplecase: +``` +$ make e2e-local +=== RUN TestE2E +STEP: Creating framework with timeout: 1200 +Running Suite: MySQL Operator E2E Suite +======================================= +Random Seed: 1640785115 - Will randomize all specs +Will run 1 of 1 specs + +Namespece test + test list namespace + /home/runkecheng/goCode/src/radondb-mysql-kubernetes/test/e2e/simplecase/list_namespace.go:38 +[BeforeEach] Namespece test + /home/runkecheng/goCode/src/radondb-mysql-kubernetes/test/e2e/framework/framework.go:62 +STEP: creating a kubernetes client +STEP: create a namespace api object (e2e-mc-1-cnkbs) +[BeforeEach] Namespece test + /home/runkecheng/goCode/src/radondb-mysql-kubernetes/test/e2e/simplecase/list_namespace.go:34 +STEP: before each +[It] test list namespace + /home/runkecheng/goCode/src/radondb-mysql-kubernetes/test/e2e/simplecase/list_namespace.go:38 +default +kube-public +kube-system +kubesphere-controls-system +kubesphere-devops-system +kubesphere-devops-worker +kubesphere-monitoring-federated +kubesphere-monitoring-system +kubesphere-system +radondb-mysql +radondb-mysql-kubernetes-system +[AfterEach] Namespece test + /home/runkecheng/goCode/src/radondb-mysql-kubernetes/test/e2e/framework/framework.go:63 +STEP: Collecting logs +STEP: Run cleanup actions +STEP: Delete testing namespace +• +Ran 1 of 1 Specs in 0.743 seconds +SUCCESS! -- 1 Passed | 0 Failed | 0 Pending | 0 Skipped +``` diff --git a/test/e2e/e2e.go b/test/e2e/e2e.go index 49956126..26433a59 100644 --- a/test/e2e/e2e.go +++ b/test/e2e/e2e.go @@ -17,9 +17,86 @@ limitations under the License. package e2e import ( + "fmt" + "os" + "path" "testing" + + "github.com/golang/glog" + . "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/config" + "github.com/onsi/ginkgo/reporters" + . "github.com/onsi/gomega" + runtimeutils "k8s.io/apimachinery/pkg/util/runtime" + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + + "github.com/radondb/radondb-mysql-kubernetes/test/e2e/framework" + "github.com/radondb/radondb-mysql-kubernetes/test/e2e/framework/ginkgowrapper" +) + +const ( + operatorNamespace = "mysql-operator" ) +var _ = SynchronizedBeforeSuite(func() []byte { + // BeforeSuite logic. + return nil +}, func(data []byte) { + // all other nodes + framework.Logf("Running BeforeSuite actions on all node") +}) + +// Similar to SynchornizedBeforeSuite, we want to run some operations only once (such as collecting cluster logs). +// Here, the order of functions is reversed; first, the function which runs everywhere, +// and then the function that only runs on the first Ginkgo node. +var _ = SynchronizedAfterSuite(func() { + // AfterSuite logic. +}, func() { + // Run only Ginkgo on node 1 + framework.Logf("Running AfterSuite actions on node 1") +}) + +// RunE2ETests checks configuration parameters (specified through flags) and then runs +// E2E tests using the Ginkgo runner. +// If a "report directory" is specified, one or more JUnit test reports will be +// generated in this directory, and cluster logs will also be saved. +// This function is called on each Ginkgo node in parallel mode. func RunE2ETests(t *testing.T) { - // empty now + runtimeutils.ReallyCrash = true + + RegisterFailHandler(ginkgowrapper.Fail) + // Disable skipped tests unless they are explicitly requested. + if len(config.GinkgoConfig.FocusStrings) == 0 && len(config.GinkgoConfig.SkipStrings) == 0 { + config.GinkgoConfig.SkipStrings = []string{`\[Flaky\]`, `\[Feature:.+\]`} + } + + rps := func() (rps []Reporter) { + // Run tests through the Ginkgo runner with output to console + JUnit for Jenkins + if framework.TestContext.ReportDir != "" { + // TODO: we should probably only be trying to create this directory once + // rather than once-per-Ginkgo-node. + if err := os.MkdirAll(framework.TestContext.ReportDir, 0755); err != nil { + glog.Errorf("Failed creating report directory: %v", err) + return + } + // add junit report + rps = append(rps, reporters.NewJUnitReporter(path.Join(framework.TestContext.ReportDir, fmt.Sprintf("junit_%v%02d.xml", "mysql_o_", config.GinkgoConfig.ParallelNode)))) + + // add logs dumper + if framework.TestContext.DumpLogsOnFailure { + rps = append(rps, NewLogsPodReporter(operatorNamespace, path.Join(framework.TestContext.ReportDir, + fmt.Sprintf("pods_logs_%d_%d.txt", config.GinkgoConfig.RandomSeed, config.GinkgoConfig.ParallelNode)))) + } + } else { + // if reportDir is not specified then print logs to stdout + if framework.TestContext.DumpLogsOnFailure { + rps = append(rps, NewLogsPodReporter(operatorNamespace, "")) + } + } + return + }() + + glog.Infof("Starting e2e run on Ginkgo node %d", config.GinkgoConfig.ParallelNode) + + RunSpecsWithDefaultAndCustomReporters(t, "MySQL Operator E2E Suite", rps) } diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 46c39523..36b846a2 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -17,38 +17,16 @@ limitations under the License. package e2e import ( - "flag" - "fmt" - "math/rand" - "os" "testing" - "time" - - "k8s.io/component-base/version" "github.com/radondb/radondb-mysql-kubernetes/test/e2e/framework" + _ "github.com/radondb/radondb-mysql-kubernetes/test/e2e/simplecase" ) -// handleFlags sets up all flags and parses the command line. -func handleFlags() { - framework.RegisterCommonFlags(flag.CommandLine) - flag.Parse() -} - -func TestMain(m *testing.M) { - var versionFlag bool - flag.CommandLine.BoolVar(&versionFlag, "version", false, "Displays version information.") - - // Register test flags, then parse flags. - handleFlags() - - if versionFlag { - fmt.Printf("%s\n", version.Get()) - os.Exit(0) - } - - rand.Seed(time.Now().UnixNano()) - os.Exit(m.Run()) +func init() { + // framework.ViperizeFlags() + testing.Init() + framework.RegisterParseFlags() } func TestE2E(t *testing.T) { diff --git a/test/e2e/framework/cleanup.go b/test/e2e/framework/cleanup.go new file mode 100644 index 00000000..55fa1d4a --- /dev/null +++ b/test/e2e/framework/cleanup.go @@ -0,0 +1,61 @@ +/* +Copyright 2016 The Kubernetes 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 framework + +import "sync" + +type CleanupActionHandle *int + +var cleanupActionsLock sync.Mutex +var cleanupActions = map[CleanupActionHandle]func(){} + +// AddCleanupAction installs a function that will be called in the event of the +// whole test being terminated. This allows arbitrary pieces of the overall +// test to hook into SynchronizedAfterSuite(). +func AddCleanupAction(fn func()) CleanupActionHandle { + p := CleanupActionHandle(new(int)) + cleanupActionsLock.Lock() + defer cleanupActionsLock.Unlock() + cleanupActions[p] = fn + return p +} + +// RemoveCleanupAction removes a function that was installed by +// AddCleanupAction. +func RemoveCleanupAction(p CleanupActionHandle) { + cleanupActionsLock.Lock() + defer cleanupActionsLock.Unlock() + delete(cleanupActions, p) +} + +// RunCleanupActions runs all functions installed by AddCleanupAction. It does +// not remove them (see RemoveCleanupAction) but it does run unlocked, so they +// may remove themselves. +func RunCleanupActions() { + list := []func(){} + func() { + cleanupActionsLock.Lock() + defer cleanupActionsLock.Unlock() + for _, fn := range cleanupActions { + list = append(list, fn) + } + }() + // Run unlocked. + for _, fn := range list { + fn() + } +} diff --git a/test/e2e/framework/framework.go b/test/e2e/framework/framework.go new file mode 100644 index 00000000..e864d15e --- /dev/null +++ b/test/e2e/framework/framework.go @@ -0,0 +1,126 @@ +/* +Copyright 2015 The Kubernetes 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 framework + +import ( + "fmt" + "time" + + "github.com/go-logr/logr" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + core "k8s.io/api/core/v1" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + + apis "github.com/radondb/radondb-mysql-kubernetes/api/v1alpha1" +) + +const ( + maxKubectlExecRetries = 5 + DefaultNamespaceDeletionTimeout = 10 * time.Minute +) + +type Framework struct { + BaseName string + Namespace *core.Namespace + + Client client.Client + ClientSet clientset.Interface + + cleanupHandle CleanupActionHandle + SkipNamespaceCreation bool + + Timeout time.Duration + + Log logr.Logger +} + +func NewFramework(baseName string) *Framework { + By(fmt.Sprintf("Creating framework with timeout: %v", TestContext.TimeoutSeconds)) + f := &Framework{ + BaseName: baseName, + SkipNamespaceCreation: false, + Log: log, + } + + BeforeEach(f.BeforeEach) + AfterEach(f.AfterEach) + + return f +} + +// BeforeEach gets a client and makes a namespace. +func (f *Framework) BeforeEach() { + // The fact that we need this feels like a bug in ginkgo. + // https://github.com/onsi/ginkgo/issues/222 + f.cleanupHandle = AddCleanupAction(f.AfterEach) + f.Timeout = time.Duration(TestContext.TimeoutSeconds) * time.Second + + By("creating a kubernetes client") + cfg, err := LoadConfig() + Expect(err).NotTo(HaveOccurred()) + + apis.AddToScheme(scheme.Scheme) + + f.Client, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + + f.ClientSet, err = clientset.NewForConfig(cfg) + Expect(err).NotTo(HaveOccurred()) + + if !f.SkipNamespaceCreation { + namespace, err := f.CreateNamespace(map[string]string{ + "e2e-framework": f.BaseName, + }) + Expect(err).NotTo(HaveOccurred()) + By(fmt.Sprintf("create a namespace api object (%s)", namespace.Name)) + + f.Namespace = namespace + } + +} + +// AfterEach deletes the namespace, after reading its events. +func (f *Framework) AfterEach() { + By("Collecting logs") + if CurrentGinkgoTestDescription().Failed && TestContext.DumpLogsOnFailure { + logFunc := Logf + // TODO: log in file if ReportDir is set + LogPodsWithLabels(f.ClientSet, f.Namespace.Name, map[string]string{}, logFunc) + } + + By("Run cleanup actions") + RemoveCleanupAction(f.cleanupHandle) + + By("Delete testing namespace") + err := DeleteNS(f.ClientSet, f.Namespace.Name, DefaultNamespaceDeletionTimeout) + if err != nil { + Failf(fmt.Sprintf("Can't delete namespace: %s", err)) + } +} + +func (f *Framework) CreateNamespace(labels map[string]string) (*core.Namespace, error) { + return CreateTestingNS(f.BaseName, f.ClientSet, labels) +} + +// WaitForPodReady waits for the pod to flip to ready in the namespace. +func (f *Framework) WaitForPodReady(podName string) error { + return waitTimeoutForPodReadyInNamespace(f.ClientSet, podName, + f.Namespace.Name, PodStartTimeout) +} diff --git a/test/e2e/framework/ginkgowrapper/BUILD b/test/e2e/framework/ginkgowrapper/BUILD new file mode 100644 index 00000000..b5eea398 --- /dev/null +++ b/test/e2e/framework/ginkgowrapper/BUILD @@ -0,0 +1,26 @@ +package(default_visibility = ["//visibility:public"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = ["wrapper.go"], + importpath = "k8s.io/kubernetes/test/e2e/framework/ginkgowrapper", + deps = ["//vendor/github.com/onsi/ginkgo:go_default_library"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], +) diff --git a/test/e2e/framework/ginkgowrapper/wrapper.go b/test/e2e/framework/ginkgowrapper/wrapper.go new file mode 100644 index 00000000..1cb3de1a --- /dev/null +++ b/test/e2e/framework/ginkgowrapper/wrapper.go @@ -0,0 +1,134 @@ +/* +Copyright 2017 The Kubernetes 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 ginkgowrapper wraps Ginkgo Fail and Skip functions to panic +// with structured data instead of a constant string. +package ginkgowrapper + +import ( + "bufio" + "bytes" + "regexp" + "runtime" + "runtime/debug" + "strings" + + "github.com/onsi/ginkgo" +) + +// FailurePanic is the value that will be panicked from Fail. +type FailurePanic struct { + Message string // The failure message passed to Fail + Filename string // The filename that is the source of the failure + Line int // The line number of the filename that is the source of the failure + FullStackTrace string // A full stack trace starting at the source of the failure +} + +// String makes FailurePanic look like the old Ginkgo panic when printed. +func (FailurePanic) String() string { return ginkgo.GINKGO_PANIC } + +// Fail wraps ginkgo.Fail so that it panics with more useful +// information about the failure. This function will panic with a +// FailurePanic. +func Fail(message string, callerSkip ...int) { + skip := 1 + if len(callerSkip) > 0 { + skip += callerSkip[0] + } + + _, file, line, _ := runtime.Caller(skip) + fp := FailurePanic{ + Message: message, + Filename: file, + Line: line, + FullStackTrace: pruneStack(skip), + } + + defer func() { + e := recover() + if e != nil { + panic(fp) + } + }() + + ginkgo.Fail(message, skip) +} + +// SkipPanic is the value that will be panicked from Skip. +type SkipPanic struct { + Message string // The failure message passed to Fail + Filename string // The filename that is the source of the failure + Line int // The line number of the filename that is the source of the failure + FullStackTrace string // A full stack trace starting at the source of the failure +} + +// String makes SkipPanic look like the old Ginkgo panic when printed. +func (SkipPanic) String() string { return ginkgo.GINKGO_PANIC } + +// Skip wraps ginkgo.Skip so that it panics with more useful +// information about why the test is being skipped. This function will +// panic with a SkipPanic. +func Skip(message string, callerSkip ...int) { + skip := 1 + if len(callerSkip) > 0 { + skip += callerSkip[0] + } + + _, file, line, _ := runtime.Caller(skip) + sp := SkipPanic{ + Message: message, + Filename: file, + Line: line, + FullStackTrace: pruneStack(skip), + } + + defer func() { + e := recover() + if e != nil { + panic(sp) + } + }() + + ginkgo.Skip(message, skip) +} + +// ginkgo adds a lot of test running infrastructure to the stack, so +// we filter those out +var stackSkipPattern = regexp.MustCompile(`onsi/ginkgo`) + +func pruneStack(skip int) string { + skip += 2 // one for pruneStack and one for debug.Stack + stack := debug.Stack() + scanner := bufio.NewScanner(bytes.NewBuffer(stack)) + var prunedStack []string + + // skip the top of the stack + for i := 0; i < 2*skip+1; i++ { + scanner.Scan() + } + + for scanner.Scan() { + if stackSkipPattern.Match(scanner.Bytes()) { + scanner.Scan() // these come in pairs + } else { + prunedStack = append(prunedStack, scanner.Text()) + scanner.Scan() // these come in pairs + prunedStack = append(prunedStack, scanner.Text()) + } + } + + return strings.Join(prunedStack, "\n") +} diff --git a/test/e2e/framework/helm.go b/test/e2e/framework/helm.go new file mode 100644 index 00000000..084b77c8 --- /dev/null +++ b/test/e2e/framework/helm.go @@ -0,0 +1,54 @@ +/* +Copyright 2021 RadonDB. + +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 framework + +import ( + "fmt" + "os" + "os/exec" + + . "github.com/onsi/gomega" +) + +func HelmInstallChart(release, ns string) { + args := []string{ + "install", release, "./" + TestContext.ChartPath, + "--namespace", ns, + "--values", TestContext.ChartValues, "--wait", + "--kube-context", TestContext.KubeContext, + "--set", fmt.Sprintf("manager.tag=%s", TestContext.OperatorImageTag), + } + + cmd := exec.Command("helm", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + Expect(cmd.Run()).Should(Succeed()) +} + +func HelmPurgeRelease(release, ns string) { + args := []string{ + "delete", release, + "--namespace", ns, + "--kube-context", TestContext.KubeContext, + } + cmd := exec.Command("helm", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + Expect(cmd.Run()).Should(Succeed()) +} diff --git a/test/e2e/framework/test_context.go b/test/e2e/framework/test_context.go index 8fcec266..511ba4b6 100644 --- a/test/e2e/framework/test_context.go +++ b/test/e2e/framework/test_context.go @@ -18,82 +18,57 @@ package framework import ( "flag" - "fmt" "os" + "github.com/onsi/ginkgo/config" "k8s.io/client-go/tools/clientcmd" ) -const ( - // TODO(gry): make sure the default port, do we need it? - defaultHost = "https://127.0.0.1:6443" - - // DefaultNumNodes is the number of nodes. If not specified, then number of nodes is auto-detected - DefaultNumNodes = -1 -) - -// TestContextType contains test settings and global state. Due to -// historic reasons, it is a mixture of items managed by the test -// framework itself, cloud providers and individual tests. -// The goal is to move anything not required by the framework -// into the code which uses the settings. -// -// The recommendation for those settings is: -// - They are stored in their own context structure or local -// variables. -// - The standard `flag` package is used to register them. -// The flag name should follow the pattern ..... -// where the prefix is unlikely to conflict with other tests or -// standard packages and each part is in lower camel case. For -// example, test/e2e/storage/csi/context.go could define -// storage.csi.numIterations. -// - framework/config can be used to simplify the registration of -// multiple options with a single function call: -// var storageCSI { -// NumIterations `default:"1" usage:"number of iterations"` -// } -// _ config.AddOptions(&storageCSI, "storage.csi") -// - The direct use Viper in tests is possible, but discouraged because -// it only works in test suites which use Viper (which is not -// required) and the supported options cannot be -// discovered by a test suite user. -// -// Test suite authors can use framework/viper to make all command line -// parameters also configurable via a configuration file. type TestContextType struct { + KubeHost string KubeConfig string KubeContext string - Host string - OutputDir string + ReportDir string + + ChartPath string + ChartValues string - // If set to true test will dump data about the namespace in which test was running. + OperatorImageTag string + SidecarImage string + + TimeoutSeconds int DumpLogsOnFailure bool - // Disables dumping cluster log from master and nodes after all tests. - DisableLogDump bool - TimeoutSeconds int } -// TestContext should be used by all tests to access common context data. var TestContext TestContextType -// RegisterCommonFlags registers flags common to all e2e test suites. -// The flag set can be flag.CommandLine (if desired) or a custom -// flag set that then gets passed to viperconfig.ViperizeFlags. -// -// The other Register*Flags methods below can be used to add more -// test-specific flags. However, those settings then get added -// regardless whether the test is actually in the test suite. -// -// For tests that have been converted to registering their -// options themselves, copy flags from test/e2e/framework/config -// as shown in HandleFlags. -func RegisterCommonFlags(flags *flag.FlagSet) { - flags.StringVar(&TestContext.KubeConfig, clientcmd.RecommendedConfigPathFlag, os.Getenv(clientcmd.RecommendedConfigPathEnvVar), "Path to kubeconfig containing embedded authinfo.") - flags.StringVar(&TestContext.KubeContext, clientcmd.FlagContext, "", "kubeconfig context to use/override. If unset, will use value from 'current-context'") - flags.StringVar(&TestContext.Host, "host", "", fmt.Sprintf("The host, or apiserver, to connect to. Will default to %s if this argument and --kubeconfig are not set.", defaultHost)) - flags.StringVar(&TestContext.OutputDir, "e2e-output-dir", "/tmp", "Output directory for interesting/useful test data, like performance data, benchmarks, and other metrics.") - flags.BoolVar(&TestContext.DumpLogsOnFailure, "dump-logs-on-failure", true, "If set to true test will dump data about the namespace in which test was running.") - flags.BoolVar(&TestContext.DisableLogDump, "disable-log-dump", false, "If set to true, logs from master and nodes won't be gathered after test run.") - flag.IntVar(&TestContext.TimeoutSeconds, "pod-wait-timeout", 100, "Timeout to wait for a pod to be ready.") +// Register flags common to all e2e test suites. +func RegisterCommonFlags() { + // Turn on verbose by default to get spec names + config.DefaultReporterConfig.Verbose = true + + // Turn on EmitSpecProgress to get spec progress (especially on interrupt) + config.GinkgoConfig.EmitSpecProgress = true + + // Randomize specs as well as suites + config.GinkgoConfig.RandomizeAllSpecs = true + + flag.StringVar(&TestContext.KubeHost, "kubernetes-host", "", "The kubernetes host, or apiserver, to connect to") + flag.StringVar(&TestContext.KubeConfig, "kubernetes-config", os.Getenv(clientcmd.RecommendedConfigPathEnvVar), "Path to config containing embedded authinfo for kubernetes. Default value is from environment variable "+clientcmd.RecommendedConfigPathEnvVar) + flag.StringVar(&TestContext.KubeContext, "kubernetes-context", "", "config context to use for kuberentes. If unset, will use value from 'current-context'") + + flag.StringVar(&TestContext.ReportDir, "report-dir", "", "Optional directory to store junit and pod logs output in. If not specified, no junit or logs files will be output") + + flag.StringVar(&TestContext.ChartPath, "operator-chart-path", "../../charts/mysql-operator", "The chart name or path for mysql operator") + flag.StringVar(&TestContext.OperatorImageTag, "operator-image-tag", "latest", "Image tag for mysql operator.") + flag.StringVar(&TestContext.SidecarImage, "sidecar-image", "radondb/mysql-sidecar:latest", "Image path for mysql sidecar.") + + flag.IntVar(&TestContext.TimeoutSeconds, "pod-wait-timeout", 1200, "Timeout to wait for a pod to be ready.") + flag.BoolVar(&TestContext.DumpLogsOnFailure, "dump-logs-on-failure", true, "Dump pods logs when a test fails.") +} + +func RegisterParseFlags() { + RegisterCommonFlags() + flag.Parse() } diff --git a/test/e2e/framework/util.go b/test/e2e/framework/util.go new file mode 100644 index 00000000..00264a11 --- /dev/null +++ b/test/e2e/framework/util.go @@ -0,0 +1,256 @@ +/* +Copyright 2021 RadonDB. + +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 framework + +import ( + "context" + "errors" + "fmt" + "math/rand" + "strconv" + "strings" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/wait" + clientset "k8s.io/client-go/kubernetes" + restclient "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/radondb/radondb-mysql-kubernetes/test/e2e/framework/ginkgowrapper" +) + +const ( + PodStartTimeout = 1 * time.Hour + // How often to Poll pods, nodes and claims. + Poll = 2 * time.Second +) + +var log = logf.Log.WithName("framework.util") + +// CreateTestingNS should be used by every test, note that we append a common prefix to the provided test name. +// Please see NewFramework instead of using this directly. +func CreateTestingNS(baseName string, c clientset.Interface, labels map[string]string) (*corev1.Namespace, error) { + if labels == nil { + labels = map[string]string{} + } + namespaceObj := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + // use a short name because long names produce long hostnames but + // maximum allowed length by mysql is 60. + // https://dev.mysql.com/doc/refman/8.0/en/change-master-to.html + GenerateName: fmt.Sprintf("e2e-%v-", baseName), + Namespace: "", + Labels: labels, + }, + Status: corev1.NamespaceStatus{}, + } + // Be robust about making the namespace creation call. + var got *corev1.Namespace + if err := wait.PollImmediate(Poll, 30*time.Second, func() (bool, error) { + var err error + got, err = c.CoreV1().Namespaces().Create(context.TODO(), namespaceObj, metav1.CreateOptions{}) + if err != nil { + Logf("Unexpected error while creating namespace: %v", err) + return false, nil + } + return true, nil + }); err != nil { + return nil, err + } + + return got, nil +} + +func RestclientConfig(kubeContext string) (*clientcmdapi.Config, error) { + if TestContext.KubeConfig == "" { + return nil, fmt.Errorf("KubeConfig must be specified to load client config") + } + c, err := clientcmd.LoadFromFile(TestContext.KubeConfig) + if err != nil { + return nil, fmt.Errorf("error loading KubeConfig: %v", err.Error()) + } + if kubeContext != "" { + c.CurrentContext = kubeContext + } + return c, nil +} + +func LoadConfig() (*restclient.Config, error) { + c, err := RestclientConfig(TestContext.KubeContext) + if err != nil { + if TestContext.KubeConfig == "" { + return restclient.InClusterConfig() + } else { + return nil, err + } + } + + return clientcmd.NewDefaultClientConfig(*c, &clientcmd.ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: TestContext.KubeHost}}).ClientConfig() +} + +func waitTimeoutForPodReadyInNamespace(c clientset.Interface, podName, namespace string, timeout time.Duration) error { + return wait.PollImmediate(Poll, timeout, podRunningAndReady(c, podName, namespace)) +} + +func podRunningAndReady(c clientset.Interface, podName, namespace string) wait.ConditionFunc { + return func() (bool, error) { + pod, err := c.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{}) + if err != nil { + return false, err + } + return podRunningAndReadyByPhase(*pod) + } +} + +func podRunningAndReadyByPhase(pod corev1.Pod) (bool, error) { + switch pod.Status.Phase { + case corev1.PodFailed, corev1.PodSucceeded: + return false, errors.New("pod completed") + case corev1.PodRunning: + for _, cond := range pod.Status.Conditions { + if cond.Type != corev1.PodReady { + continue + } + return cond.Status == corev1.ConditionTrue, nil + } + return false, errors.New("pod ready condition not found") + } + return false, nil +} + +// deleteNS deletes the provided namespace, waits for it to be completely deleted, and then checks +// whether there are any pods remaining in a non-terminating state. +func DeleteNS(c clientset.Interface, namespace string, timeout time.Duration) error { + startTime := time.Now() + if err := c.CoreV1().Namespaces().Delete(context.TODO(), namespace, metav1.DeleteOptions{}); err != nil { + return err + } + + // wait for namespace to delete or timeout. + // err := wait.PollImmediate(2*time.Second, timeout, func() (bool, error) { + // if _, err := c.CoreV1().Namespaces().Get(namespace, metav1.GetOptions{}); err != nil { + // if apierrs.IsNotFound(err) { + // return true, nil + // } + // Logf("Error while waiting for namespace to be terminated: %v", err) + // return false, nil + // } + // return false, nil + // }) + + Logf("namespace %v deletion completed in %s", namespace, time.Since(startTime)) + return nil +} + +func Logf(format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + log.Info(msg) +} + +func Failf(format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + ginkgowrapper.Fail(nowStamp()+": "+msg, 2) +} + +func nowStamp() string { + return time.Now().Format(time.StampMilli) +} + +func GetPodLogs(c clientset.Interface, namespace, podName, containerName string) (string, error) { + return getPodLogsInternal(c, namespace, podName, containerName, false) +} + +func getPreviousPodLogs(c clientset.Interface, namespace, podName, containerName string) (string, error) { + return getPodLogsInternal(c, namespace, podName, containerName, true) +} + +// utility function for gomega Eventually +func getPodLogsInternal(c clientset.Interface, namespace, podName, containerName string, previous bool) (string, error) { + logs, err := c.CoreV1().RESTClient().Get(). + Resource("pods"). + Namespace(namespace). + Name(podName).SubResource("log"). + Param("container", containerName). + Param("previous", strconv.FormatBool(previous)). + Do(context.TODO()). + Raw() + if err != nil { + return "", err + } + if err == nil && strings.Contains(string(logs), "Internal Error") { + return "", fmt.Errorf("fetched log contains \"Internal Error\": %q", string(logs)) + } + return string(logs), err +} + +func kubectlLogPod(c clientset.Interface, pod corev1.Pod, containerNameSubstr string, logFunc func(ftm string, args ...interface{})) { + for _, container := range pod.Spec.Containers { + if strings.Contains(container.Name, containerNameSubstr) { + // Contains() matches all strings if substr is empty + logs, err := GetPodLogs(c, pod.Namespace, pod.Name, container.Name) + if err != nil { + logFunc("Failed to get logs of pod %v, container %v, err: %v", pod.Name, container.Name, err) + } + plogs, err := getPreviousPodLogs(c, pod.Namespace, pod.Name, container.Name) + plogs = "PREVIOUS\n" + plogs + if err != nil { + plogs = fmt.Sprintf("Failed to get previous logs for pod %v, container %v, err: %v", pod.Name, container.Name, err) + } + logFunc("Logs of %v/%v:%v on node %v", pod.Namespace, pod.Name, container.Name, pod.Spec.NodeName) + logFunc("%s : %s \nSTARTLOG\n%s\nENDLOG for container %v:%v:%v", plogs, containerNameSubstr, logs, pod.Namespace, pod.Name, container.Name) + } + } +} + +func LogPodsWithLabels(c clientset.Interface, ns string, match map[string]string, logFunc func(ftm string, args ...interface{})) { + podList, err := c.CoreV1().Pods(ns).List(context.TODO(), metav1.ListOptions{LabelSelector: labels.SelectorFromSet(match).String()}) + if err != nil { + logFunc("Error getting pods in namespace %q: %v", ns, err) + return + } + logFunc("Running kubectl logs on pods with labels %v in %v", match, ns) + for _, pod := range podList.Items { + kubectlLogPod(c, pod, "", logFunc) + } +} + +func LogContainersInPodsWithLabels(c clientset.Interface, ns string, match map[string]string, containerSubstr string, logFunc func(ftm string, args ...interface{})) { + podList, err := c.CoreV1().Pods(ns).List(context.TODO(), metav1.ListOptions{LabelSelector: labels.SelectorFromSet(match).String()}) + if err != nil { + Logf("Error getting pods in namespace %q: %v", ns, err) + return + } + for _, pod := range podList.Items { + kubectlLogPod(c, pod, containerSubstr, logFunc) + } +} + +func RandStr(length int) string { + str := "abcdefghijklmnopqrstuvwxyz" + bytes := []byte(str) + result := []byte{} + rand.Seed(time.Now().UnixNano() + int64(rand.Intn(100))) + for i := 0; i < length; i++ { + result = append(result, bytes[rand.Intn(len(bytes))]) + } + return string(result) +} diff --git a/test/e2e/reporter.go b/test/e2e/reporter.go new file mode 100644 index 00000000..bad5e5eb --- /dev/null +++ b/test/e2e/reporter.go @@ -0,0 +1,155 @@ +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package e2e + +import ( + "context" + "fmt" + "io" + "os" + "strconv" + "time" + + "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/config" + "github.com/onsi/ginkgo/reporters" + "github.com/onsi/ginkgo/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + clientset "k8s.io/client-go/kubernetes" + + "github.com/radondb/radondb-mysql-kubernetes/test/e2e/framework" +) + +type podLogReporter struct { + namespace string + + logPath string + logFile *os.File + + out io.Writer +} + +var radondbMysqlTestLabel = map[string]string{ + "app.kubernetes.io/managed-by": "mysql.radondb.com", +} + +// NewLogsPodReporter writes the logs for all pods in the specified namespace. +// if path is specified then the logs are written to that path, else logs are +// written to GinkgoWriter +func NewLogsPodReporter(ns, path string) reporters.Reporter { + return &podLogReporter{ + namespace: ns, + logPath: path, + out: ginkgo.GinkgoWriter, + } +} + +// called when suite starts +func (r *podLogReporter) SpecSuiteWillBegin(config config.GinkgoConfigType, s *types.SuiteSummary) { + if r.logPath != "" { + var err error + r.logFile, err = os.OpenFile(r.logPath, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + fmt.Printf("Failed to open file: %s with error: %s\n", r.logPath, err) + return + } + + r.out = r.logFile + } +} + +// called before BeforeSuite before starting tests +func (r *podLogReporter) BeforeSuiteDidRun(setupSummary *types.SetupSummary) {} + +// called before every test +func (r *podLogReporter) SpecWillRun(specSummary *types.SpecSummary) {} + +// called after every test +func (r *podLogReporter) SpecDidComplete(specSummary *types.SpecSummary) { + // don't output logs if test didn't failed + if specSummary.State <= types.SpecStatePassed { + return + } + + // get the kubernetes client + kubeCfg, err := framework.LoadConfig() + if err != nil { + fmt.Println("Failed to get kubeconfig!") + return + } + + client, err := clientset.NewForConfig(kubeCfg) + if err != nil { + fmt.Println("Failed to create k8s client!") + return + } + + fmt.Fprintf(r.out, "## Start test: %v\n", specSummary.ComponentTexts) + + LogPodsWithLabels(client, r.namespace, radondbMysqlTestLabel, specSummary.RunTime, r.out) + + fmt.Fprintf(r.out, "## END test\n") + +} + +// called before AfterSuite runs +func (r *podLogReporter) AfterSuiteDidRun(setupSummary *types.SetupSummary) {} + +// caleed at the end +func (r *podLogReporter) SpecSuiteDidEnd(summary *types.SuiteSummary) { + if r.logFile != nil { + r.logFile.Close() + } +} + +func LogPodsWithLabels(c clientset.Interface, ns string, match map[string]string, since time.Duration, out io.Writer) { + podList, err := c.CoreV1().Pods(ns).List(context.TODO(), metav1.ListOptions{LabelSelector: labels.SelectorFromSet(match).String()}) + if err != nil { + fmt.Fprintf(out, "error listing pods: %s", err) + return + } + + for _, pod := range podList.Items { + for _, container := range pod.Spec.Containers { + fmt.Fprintf(out, "\n\n===============\nSTART LOGS for %s (%s):\n", pod.Name, container.Name) + runLogs(c, ns, pod.Name, container.Name, false, since, out) + fmt.Fprintf(out, "\n\n===============\nSTOP LOGS for %s (%s):\n", pod.Name, container.Name) + } + } +} + +func runLogs(client clientset.Interface, namespace, name, container string, previous bool, sinceStart time.Duration, out io.Writer) error { + req := client.CoreV1().RESTClient().Get(). + Namespace(namespace). + Name(name). + Resource("pods"). + SubResource("log"). + Param("container", container). + Param("previous", strconv.FormatBool(previous)). + Param("since", strconv.FormatInt(int64(sinceStart.Round(time.Second).Seconds()), 10)) + + readCloser, err := req.Stream(context.TODO()) + if err != nil { + return err + } + + defer readCloser.Close() + _, err = io.Copy(out, readCloser) + return err + +} diff --git a/test/e2e/simplecase/list_namespace.go b/test/e2e/simplecase/list_namespace.go new file mode 100644 index 00000000..790763aa --- /dev/null +++ b/test/e2e/simplecase/list_namespace.go @@ -0,0 +1,46 @@ +/* +Copyright 2021 RadonDB. + +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 simplecase + +import ( + "context" + "fmt" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/radondb/radondb-mysql-kubernetes/test/e2e/framework" +) + +// Simple and quick test case, used to verify that the E2E framework is available. +var _ = Describe("Namespece test", func() { + f := framework.NewFramework("mc-1") + + BeforeEach(func() { + By("before each") + }) + + It("test list namespace", func() { + namespaces, err := f.ClientSet.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{}) + Expect(err).Should(BeNil()) + Expect(len(namespaces.Items)).ShouldNot(BeZero()) + for _, ns := range namespaces.Items { + fmt.Fprintln(GinkgoWriter, ns.Name) + } + }) +}) diff --git a/test/e2e/staticcheck.conf b/test/e2e/staticcheck.conf new file mode 100644 index 00000000..f32065c0 --- /dev/null +++ b/test/e2e/staticcheck.conf @@ -0,0 +1 @@ +checks=[ "all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-ST1001"]