github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/backuprepo/create_test.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package backuprepo 21 22 import ( 23 "encoding/json" 24 "fmt" 25 "net/http" 26 "strings" 27 28 . "github.com/onsi/ginkgo/v2" 29 . "github.com/onsi/gomega" 30 31 "github.com/spf13/cobra" 32 "github.com/spf13/pflag" 33 corev1 "k8s.io/api/core/v1" 34 "k8s.io/apimachinery/pkg/runtime" 35 "k8s.io/cli-runtime/pkg/genericiooptions" 36 "k8s.io/cli-runtime/pkg/resource" 37 "k8s.io/client-go/dynamic/fake" 38 clientfake "k8s.io/client-go/rest/fake" 39 cmdtesting "k8s.io/kubectl/pkg/cmd/testing" 40 41 "github.com/1aal/kubeblocks/pkg/cli/scheme" 42 "github.com/1aal/kubeblocks/pkg/cli/testing" 43 ) 44 45 var _ = Describe("backuprepo create command", func() { 46 var streams genericiooptions.IOStreams 47 var tf *cmdtesting.TestFactory 48 var cmd *cobra.Command 49 var options *createOptions 50 51 BeforeEach(func() { 52 streams, _, _, _ = genericiooptions.NewTestIOStreams() 53 tf = cmdtesting.NewTestFactory().WithNamespace(testing.Namespace) 54 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) 55 httpResp := func(obj runtime.Object) *http.Response { 56 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, obj)} 57 } 58 59 tf.UnstructuredClient = &clientfake.RESTClient{ 60 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, 61 Client: clientfake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { 62 urlPrefix := "/api/v1/namespaces/" + testing.Namespace 63 if req.URL.Path == urlPrefix+"/secrets" && req.Method == http.MethodPost { 64 dec := json.NewDecoder(req.Body) 65 secret := &corev1.Secret{} 66 _ = dec.Decode(secret) 67 if secret.Name == "" && secret.GenerateName != "" { 68 secret.Name = secret.GenerateName + "123456" 69 } 70 return httpResp(secret), nil 71 } 72 if strings.HasPrefix(req.URL.Path, urlPrefix+"/secrets") && req.Method == http.MethodPatch { 73 return httpResp(&corev1.Secret{}), nil 74 } 75 mapping := map[string]*http.Response{ 76 "/api/v1/secrets": httpResp(&corev1.SecretList{}), 77 } 78 return mapping[req.URL.Path], nil 79 }), 80 } 81 tf.Client = tf.UnstructuredClient 82 83 options = &createOptions{} 84 cmd = newCreateCommand(options, tf, streams) 85 err := options.init(tf) 86 Expect(err).ShouldNot(HaveOccurred()) 87 88 providerObj := testing.FakeStorageProvider("fake-s3", nil) 89 repoObj := testing.FakeBackupRepo("test-backuprepo", false) 90 tf.FakeDynamicClient = fake.NewSimpleDynamicClient( 91 scheme.Scheme, providerObj, repoObj) 92 err = options.init(tf) 93 Expect(err).ShouldNot(HaveOccurred()) 94 }) 95 96 AfterEach(func() { 97 tf.Cleanup() 98 }) 99 100 Describe("parseProviderFlags", func() { 101 It("should fail if --provider is not specified", func() { 102 err := options.parseProviderFlags(cmd, []string{}, tf) 103 Expect(err).Should(MatchError(ContainSubstring("please specify the --provider flag"))) 104 }) 105 106 It("should fail if the specified provider is not existing", func() { 107 err := options.parseProviderFlags(cmd, []string{"--provider", "non-existent"}, tf) 108 Expect(err).Should(MatchError(ContainSubstring("storage provider \"non-existent\" is not found"))) 109 }) 110 111 It("should able to parse flags from the provider", func() { 112 err := options.parseProviderFlags(cmd, []string{ 113 "--provider", "fake-s3", 114 "--access-key-id", "abc", 115 "--secret-access-key", "def", 116 }, tf) 117 Expect(err).ShouldNot(HaveOccurred()) 118 }) 119 120 It("should fail to parse unknown flags", func() { 121 err := options.parseProviderFlags(cmd, []string{"--provider", "fake-s3", "--foo", "abc"}, tf) 122 Expect(err).Should(MatchError(ContainSubstring("unknown flag: --foo"))) 123 }) 124 125 It("should fail if required flags are not specified", func() { 126 err := options.parseProviderFlags(cmd, []string{"--provider", "fake-s3"}, tf) 127 Expect(err).Should(MatchError(ContainSubstring("required flag(s) \"access-key-id\", \"secret-access-key\" not set"))) 128 }) 129 130 It("should set isDefault field", func() { 131 err := options.parseProviderFlags(cmd, []string{ 132 "--provider", "fake-s3", "--access-key-id", "abc", "--secret-access-key", "def", "--default", 133 }, tf) 134 Expect(err).ShouldNot(HaveOccurred()) 135 Expect(options.isDefault).Should(BeTrue()) 136 }) 137 138 It("should return ErrHelp if --help is specified", func() { 139 err := options.parseProviderFlags(cmd, []string{"--provider", "fake-s3", "--help"}, tf) 140 Expect(err).Should(MatchError(pflag.ErrHelp)) 141 }) 142 }) 143 144 Describe("complete", func() { 145 It("should set fields in createOptions", func() { 146 err := options.parseProviderFlags(cmd, []string{ 147 "test-backuprepo", 148 "--provider", "fake-s3", 149 "--access-key-id", "abc", 150 "--secret-access-key", "def", 151 "--region", "us-west-2", 152 "--bucket", "test-bucket", 153 }, tf) 154 Expect(err).ShouldNot(HaveOccurred()) 155 err = options.complete(cmd) 156 Expect(err).ShouldNot(HaveOccurred()) 157 Expect(options.repoName).Should(Equal("test-backuprepo")) 158 Expect(options.allValues).Should(Equal(map[string]string{ 159 "accessKeyId": "abc", 160 "secretAccessKey": "def", 161 "region": "us-west-2", 162 "bucket": "test-bucket", 163 "endpoint": "", 164 "mountOptions": "", 165 })) 166 Expect(options.config).Should(Equal(map[string]string{ 167 "region": "us-west-2", 168 "bucket": "test-bucket", 169 "endpoint": "", 170 "mountOptions": "", 171 })) 172 Expect(options.credential).Should(Equal(map[string]string{ 173 "accessKeyId": "abc", 174 "secretAccessKey": "def", 175 })) 176 }) 177 }) 178 179 Describe("validate", func() { 180 BeforeEach(func() { 181 err := options.parseProviderFlags(cmd, []string{ 182 "--provider", "fake-s3", "--access-key-id", "abc", "--secret-access-key", "def", 183 }, tf) 184 Expect(err).ShouldNot(HaveOccurred()) 185 err = options.complete(cmd) 186 Expect(err).ShouldNot(HaveOccurred()) 187 }) 188 189 It("should validate parameters by the json schema", func() { 190 options.allValues["region"] = "invalid-region" 191 err := options.validate(cmd) 192 Expect(err).Should(MatchError(fmt.Errorf("invalid flags"))) 193 }) 194 It("should validate pv reclaim policy", func() { 195 options.pvReclaimPolicy = "whatever" 196 err := options.validate(cmd) 197 Expect(err).Should(MatchError(ContainSubstring("invalid --pv-reclaim-policy"))) 198 }) 199 It("should validate volume capacity", func() { 200 options.volumeCapacity = "invalid" 201 err := options.validate(cmd) 202 Expect(err).Should(MatchError(ContainSubstring("invalid --volume-capacity"))) 203 }) 204 It("should validate the backup repo is not existing", func() { 205 options.repoName = "test-backuprepo" 206 err := options.validate(cmd) 207 Expect(err).Should(MatchError(ContainSubstring("BackupRepo \"test-backuprepo\" is already exists"))) 208 }) 209 It("should validate if there is a default backup repo", func() { 210 By("setting up a default backup repo") 211 providerObj := testing.FakeStorageProvider("fake-s3", nil) 212 repoObj := testing.FakeBackupRepo("test-backuprepo", true) 213 tf.FakeDynamicClient = fake.NewSimpleDynamicClient( 214 scheme.Scheme, providerObj, repoObj) 215 err := options.init(tf) 216 Expect(err).ShouldNot(HaveOccurred()) 217 218 By("validating") 219 options.isDefault = true 220 err = options.validate(cmd) 221 Expect(err).Should(MatchError(ContainSubstring("there is already a default backup repo"))) 222 }) 223 Context("validate access method", func() { 224 const supported = "supported" 225 BeforeEach(func() { 226 options.providerObject.Spec.StorageClassTemplate = "" 227 options.providerObject.Spec.PersistentVolumeClaimTemplate = "" 228 options.providerObject.Spec.DatasafedConfigTemplate = "" 229 options.accessMethod = "" // unspecified 230 }) 231 It("should return error if the provider doesn't support any access method", func() { 232 Expect(options.supportedAccessMethods()).Should(BeEmpty()) 233 err := options.validate(cmd) 234 Expect(err).Should(MatchError(ContainSubstring("it doesn't support any access method"))) 235 }) 236 It("should use the mount method if it's the only supported access method", func() { 237 options.providerObject.Spec.StorageClassTemplate = supported 238 err := options.validate(cmd) 239 Expect(err).ShouldNot(HaveOccurred()) 240 Expect(options.accessMethod).Should(Equal("Mount")) 241 }) 242 It("should use the tool method if it's the only supported access method", func() { 243 options.providerObject.Spec.DatasafedConfigTemplate = supported 244 err := options.validate(cmd) 245 Expect(err).ShouldNot(HaveOccurred()) 246 Expect(options.accessMethod).Should(Equal("Tool")) 247 }) 248 It("should return error if the specified access method is not supported", func() { 249 options.providerObject.Spec.StorageClassTemplate = supported 250 options.accessMethod = "Tool" 251 err := options.validate(cmd) 252 Expect(err).Should(MatchError(ContainSubstring("doesn't support \"Tool\" access method"))) 253 }) 254 It("should prefer using the tool method", func() { 255 options.providerObject.Spec.StorageClassTemplate = supported 256 options.providerObject.Spec.DatasafedConfigTemplate = supported 257 options.accessMethod = "" 258 err := options.validate(cmd) 259 Expect(err).ShouldNot(HaveOccurred()) 260 Expect(options.accessMethod).Should(Equal("Tool")) 261 }) 262 }) 263 }) 264 265 Describe("run", func() { 266 It("should success", func() { 267 By("preparing the options") 268 err := options.parseProviderFlags(cmd, []string{ 269 "--provider", "fake-s3", "--access-key-id", "abc", "--secret-access-key", "def", 270 "--region", "us-west-1", "--bucket", "test-bucket", "--default", "--access-method", "Mount", 271 }, tf) 272 Expect(err).ShouldNot(HaveOccurred()) 273 err = options.complete(cmd) 274 Expect(err).ShouldNot(HaveOccurred()) 275 err = options.validate(cmd) 276 Expect(err).ShouldNot(HaveOccurred()) 277 278 By("running the command") 279 err = options.run() 280 Expect(err).ShouldNot(HaveOccurred()) 281 }) 282 }) 283 })