github.com/verrazzano/verrazzano@v1.7.0/tools/vz/cmd/bugreport/bugreport_test.go (about) 1 // Copyright (c) 2022, 2023, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package bugreport 5 6 import ( 7 "context" 8 "fmt" 9 "os" 10 "testing" 11 12 "github.com/spf13/cobra" 13 "github.com/stretchr/testify/assert" 14 vzconstants "github.com/verrazzano/verrazzano/pkg/constants" 15 "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1beta1" 16 "github.com/verrazzano/verrazzano/tools/vz/pkg/constants" 17 pkghelper "github.com/verrazzano/verrazzano/tools/vz/pkg/helpers" 18 "github.com/verrazzano/verrazzano/tools/vz/test/helpers" 19 appsv1 "k8s.io/api/apps/v1" 20 corev1 "k8s.io/api/core/v1" 21 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 "k8s.io/apimachinery/pkg/types" 23 "k8s.io/cli-runtime/pkg/genericclioptions" 24 dynfake "k8s.io/client-go/dynamic/fake" 25 "sigs.k8s.io/controller-runtime/pkg/client" 26 "sigs.k8s.io/controller-runtime/pkg/client/fake" 27 ) 28 29 const ( 30 testKubeConfig = "kubeconfig" 31 testK8sContext = "testcontext" 32 33 // captureResourceErrMsg = "Capturing resources from the cluster" 34 // sensitiveDataErrMsg = "WARNING: Please examine the contents of the bug report for any sensitive data" 35 // captureLogErrMsg = "Capturing log from pod verrazzano-platform-operator in verrazzano-install namespace" 36 // dummyNamespaceErrMsg = "Namespace dummy not found in the cluster" 37 ) 38 39 // TestBugReportHelp 40 // GIVEN a CLI bug-report command 41 // WHEN I call cmd.Help for bug-report 42 // THEN expect the help for the command in the standard output 43 func TestBugReportHelp(t *testing.T) { 44 stdoutFile, stderrFile := createStdTempFiles(t) 45 defer os.Remove(stdoutFile.Name()) 46 defer os.Remove(stderrFile.Name()) 47 48 rc := helpers.NewFakeRootCmdContext(genericclioptions.IOStreams{In: os.Stdin, Out: stdoutFile, ErrOut: stderrFile}) 49 cmd := NewCmdBugReport(rc) 50 assert.NotNil(t, cmd) 51 err := cmd.Help() 52 if err != nil { 53 assert.Error(t, err) 54 } 55 56 buf, err := os.ReadFile(stdoutFile.Name()) 57 assert.NoError(t, err) 58 assert.Contains(t, string(buf), "Verrazzano command line utility to collect data from the cluster, to report an issue") 59 } 60 61 // TestBugReportExistingReportFile 62 // GIVEN a CLI bug-report command using an existing file for flag --report-file 63 // WHEN I call cmd.Execute for bug-report 64 // THEN expect an error 65 func TestBugReportExistingReportFile(t *testing.T) { 66 cmd := setUpAndVerifyResources(t) 67 68 tmpDir, _ := os.MkdirTemp("", "bug-report") 69 defer cleanupTempDir(t, tmpDir) 70 71 // Define and create the bug report file 72 reportFile := "bug-report.tgz" 73 bugRepFile, err := os.Create(tmpDir + string(os.PathSeparator) + reportFile) 74 if err != nil { 75 assert.Error(t, err) 76 } 77 defer cleanupFile(t, bugRepFile) 78 79 setUpGlobalFlags(cmd) 80 err = cmd.PersistentFlags().Set(constants.BugReportFileFlagName, bugRepFile.Name()) 81 assert.NoError(t, err) 82 err = cmd.Execute() 83 assert.NotNil(t, err) 84 assert.Contains(t, err.Error(), "file exists") 85 } 86 87 // TestBugReportExistingDir 88 // GIVEN a CLI bug-report command with flag --report-file pointing to an existing directory 89 // WHEN I call cmd.Execute for bug-report 90 // THEN expect an error 91 func TestBugReportExistingDir(t *testing.T) { 92 cmd := setUpAndVerifyResources(t) 93 94 tmpDir, _ := os.MkdirTemp("", "bug-report") 95 defer cleanupTempDir(t, tmpDir) 96 97 reportDir := tmpDir + string(os.PathSeparator) + "test-report" 98 if err := os.Mkdir(reportDir, os.ModePerm); err != nil { 99 assert.Error(t, err) 100 } 101 102 setUpGlobalFlags(cmd) 103 err := cmd.PersistentFlags().Set(constants.BugReportFileFlagName, reportDir) 104 assert.NoError(t, err) 105 err = cmd.Execute() 106 assert.NotNil(t, err) 107 assert.Contains(t, err.Error(), "file exists") 108 } 109 110 // TestBugReportNonExistingFileDir 111 // GIVEN a CLI bug-report command with flag --report-file pointing to a file, where the directory doesn't exist 112 // WHEN I call cmd.Execute for bug-report 113 // THEN expect an error 114 func TestBugReportNonExistingFileDir(t *testing.T) { 115 cmd := setUpAndVerifyResources(t) 116 117 tmpDir, _ := os.MkdirTemp("", "bug-report") 118 defer cleanupTempDir(t, tmpDir) 119 120 reportDir := tmpDir + string(os.PathSeparator) + "test-report" 121 reportFile := reportDir + string(os.PathSeparator) + string(os.PathSeparator) + "bug-report.tgz" 122 123 setUpGlobalFlags(cmd) 124 err := cmd.PersistentFlags().Set(constants.BugReportFileFlagName, reportFile) 125 assert.NoError(t, err) 126 err = cmd.Execute() 127 assert.NotNil(t, err) 128 assert.Contains(t, err.Error(), "no such file or directory") 129 } 130 131 // TestBugReportFileNoPermission 132 // GIVEN a CLI bug-report command with flag --report-file pointing to a file, where there is no write permission 133 // WHEN I call cmd.Execute for bug-report 134 // THEN expect an error 135 func TestBugReportFileNoPermission(t *testing.T) { 136 cmd := setUpAndVerifyResources(t) 137 138 tmpDir, _ := os.MkdirTemp("", "bug-report") 139 defer cleanupTempDir(t, tmpDir) 140 141 reportDir := tmpDir + string(os.PathSeparator) + "test-report" 142 // Create a directory with only read permission 143 if err := os.Mkdir(reportDir, 0400); err != nil { 144 assert.Error(t, err) 145 } 146 reportFile := reportDir + string(os.PathSeparator) + "bug-report.tgz" 147 setUpGlobalFlags(cmd) 148 err := cmd.PersistentFlags().Set(constants.BugReportFileFlagName, reportFile) 149 assert.NoError(t, err) 150 err = cmd.Execute() 151 assert.NotNil(t, err) 152 assert.Contains(t, err.Error(), "permission denied") 153 } 154 155 // TestBugReportSuccess 156 // GIVEN a CLI bug-report command with multiple flags 157 // WHEN I call cmd.Execute 158 // THEN expect the command to show the resources captured in the standard output and create the bug report file 159 func TestBugReportSuccess(t *testing.T) { 160 cmd := setUpAndVerifyResources(t) 161 162 tmpDir, _ := os.MkdirTemp("", "bug-report") 163 defer cleanupTempDir(t, tmpDir) 164 165 bugRepFile := tmpDir + string(os.PathSeparator) + "bug-report.tgz" 166 setUpGlobalFlags(cmd) 167 err := cmd.PersistentFlags().Set(constants.BugReportFileFlagName, bugRepFile) 168 assert.NoError(t, err) 169 err = cmd.PersistentFlags().Set(constants.BugReportIncludeNSFlagName, "dummy,verrazzano-install,default") 170 assert.NoError(t, err) 171 err = cmd.PersistentFlags().Set(constants.VerboseFlag, "true") 172 assert.NoError(t, err) 173 err = cmd.Execute() 174 if err != nil { 175 assert.Error(t, err) 176 } 177 assert.NoError(t, err) 178 } 179 180 // TestDefaultBugReportSuccess 181 // GIVEN a CLI bug-report command with no flags (default) 182 // WHEN I call cmd.Execute from user permissive directory 183 // THEN expect the command to show the resources captured in the standard output and create the bug report file in current dir 184 func TestDefaultBugReportSuccess(t *testing.T) { 185 c := getClientWithVZWatch() 186 187 // Verify the vz resource is as expected 188 vz := v1beta1.Verrazzano{} 189 err := c.Get(context.TODO(), types.NamespacedName{Namespace: "default", Name: "verrazzano"}, &vz) 190 assert.NoError(t, err) 191 192 stdoutFile, stderrFile := createStdTempFiles(t) 193 defer os.Remove(stdoutFile.Name()) 194 defer os.Remove(stderrFile.Name()) 195 196 rc := setupFakeDynamicClient(c, genericclioptions.IOStreams{In: os.Stdin, Out: stdoutFile, ErrOut: stderrFile}) 197 rc.SetClient(c) 198 cmd := NewCmdBugReport(rc) 199 assert.NotNil(t, cmd) 200 setUpGlobalFlags(cmd) 201 err = cmd.Execute() 202 assert.Nil(t, err) 203 204 if !pkghelper.CheckAndRemoveBugReportExistsInDir("") { 205 t.Fatal("cannot find bug report file in current directory") 206 } 207 } 208 209 // TestDefaultBugReportSuccess 210 // GIVEN a CLI bug-report command with no flags (default) 211 // WHEN I call cmd.Execute from read only directory 212 // THEN expect the command to show the resources captured in the standard output and create the bug report file in tmp dir 213 func TestDefaultBugReportReadOnlySuccess(t *testing.T) { 214 c := getClientWithVZWatch() 215 216 // Verify the vz resource is as expected 217 vz := v1beta1.Verrazzano{} 218 err := c.Get(context.TODO(), types.NamespacedName{Namespace: "default", Name: "verrazzano"}, &vz) 219 assert.NoError(t, err) 220 221 stdoutFile, stderrFile := createStdTempFiles(t) 222 defer os.Remove(stdoutFile.Name()) 223 defer os.Remove(stderrFile.Name()) 224 225 rc := setupFakeDynamicClient(c, genericclioptions.IOStreams{In: os.Stdin, Out: stdoutFile, ErrOut: stderrFile}) 226 rc.SetClient(c) 227 cmd := NewCmdBugReport(rc) 228 assert.NotNil(t, cmd) 229 setUpGlobalFlags(cmd) 230 pwd, err := os.Getwd() 231 assert.Nil(t, err) 232 assert.Nil(t, os.Chdir("/")) 233 defer os.Chdir(pwd) 234 235 err = cmd.Execute() 236 assert.Nil(t, err) 237 238 if !pkghelper.CheckAndRemoveBugReportExistsInDir(os.TempDir() + "/") { 239 t.Fatal("cannot find bug report file in temp directory") 240 } 241 } 242 243 // TestBugReportDefaultReportFile 244 // GIVEN a CLI bug-report command 245 // WHEN I call cmd.Execute, without specifying --report-file 246 // THEN expect the command to create the report vz-bug-report-*.tar.gz under the current directory 247 func TestBugReportDefaultReportFile(t *testing.T) { 248 // clean up the bugreport file that is generated 249 defer func(t *testing.T) { 250 if !pkghelper.CheckAndRemoveBugReportExistsInDir("") { 251 t.Fatal("cannot find and delete bug report file in current directory") 252 } 253 }(t) 254 255 c := getClientWithVZWatch() 256 257 // Verify the vz resource is as expected 258 vz := v1beta1.Verrazzano{} 259 err := c.Get(context.TODO(), types.NamespacedName{Namespace: "default", Name: "verrazzano"}, &vz) 260 assert.NoError(t, err) 261 262 stdoutFile, err := os.CreateTemp("", "tmpstdout") 263 assert.NoError(t, err) 264 defer os.Remove(stdoutFile.Name()) 265 266 stderrFile, err := os.CreateTemp("", "tmpstderr") 267 assert.NoError(t, err) 268 defer os.Remove(stderrFile.Name()) 269 270 rc := setupFakeDynamicClient(c, genericclioptions.IOStreams{In: os.Stdin, Out: stdoutFile, ErrOut: stderrFile}) 271 rc.SetClient(c) 272 cmd := NewCmdBugReport(rc) 273 err = cmd.PersistentFlags().Set(constants.VerboseFlag, "true") 274 setUpGlobalFlags(cmd) 275 assert.NoError(t, err) 276 assert.NotNil(t, cmd) 277 err = cmd.Execute() 278 assert.NoError(t, err) 279 280 _, err = os.ReadFile(stdoutFile.Name()) 281 assert.NoError(t, err) 282 } 283 284 // TestBugReportNoVerrazzano 285 // GIVEN a CLI bug-report command 286 // WHEN I call cmd.Execute without Verrazzano installed 287 // THEN expect the command to generate bug report 288 func TestBugReportNoVerrazzano(t *testing.T) { 289 c := getClientWithWatch() 290 stdoutFile, stderrFile := createStdTempFiles(t) 291 defer os.Remove(stdoutFile.Name()) 292 defer os.Remove(stderrFile.Name()) 293 294 rc := setupFakeDynamicClient(c, genericclioptions.IOStreams{In: os.Stdin, Out: stdoutFile, ErrOut: stderrFile}) 295 rc.SetClient(c) 296 cmd := NewCmdBugReport(rc) 297 assert.NotNil(t, cmd) 298 299 tmpDir, _ := os.MkdirTemp("", "bug-report") 300 defer cleanupTempDir(t, tmpDir) 301 302 bugRepFile := tmpDir + string(os.PathSeparator) + "bug-report.tgz" 303 setUpGlobalFlags(cmd) 304 err := cmd.PersistentFlags().Set(constants.BugReportFileFlagName, bugRepFile) 305 assert.NoError(t, err) 306 err = cmd.PersistentFlags().Set(constants.BugReportIncludeNSFlagName, "dummy,verrazzano-install") 307 assert.NoError(t, err) 308 err = cmd.Execute() 309 if err != nil { 310 assert.Error(t, err) 311 } 312 313 errBuf, err := os.ReadFile(stderrFile.Name()) 314 assert.NoError(t, err) 315 assert.NotContains(t, string(errBuf), "Verrazzano is not installed") 316 assert.FileExists(t, bugRepFile) 317 } 318 319 // TestBugReportFailureUsingInvalidClient 320 // GIVEN a CLI bug-report command 321 // WHEN I call cmd.Execute without Verrazzano installed and using an invalid client 322 // THEN expect the command to fail with a message indicating Verrazzano is not installed and no resource captured 323 func TestBugReportFailureUsingInvalidClient(t *testing.T) { 324 c := getInvalidClient() 325 stdoutFile, stderrFile := createStdTempFiles(t) 326 defer os.Remove(stdoutFile.Name()) 327 defer os.Remove(stderrFile.Name()) 328 329 rc := setupFakeDynamicClient(c, genericclioptions.IOStreams{In: os.Stdin, Out: stdoutFile, ErrOut: stderrFile}) 330 rc.SetClient(c) 331 cmd := NewCmdBugReport(rc) 332 assert.NotNil(t, cmd) 333 334 tmpDir, _ := os.MkdirTemp("", "bug-report") 335 defer cleanupTempDir(t, tmpDir) 336 337 bugRepFile := tmpDir + string(os.PathSeparator) + "bug-report.tgz" 338 setUpGlobalFlags(cmd) 339 err := cmd.PersistentFlags().Set(constants.BugReportFileFlagName, bugRepFile) 340 assert.NoError(t, err) 341 err = cmd.PersistentFlags().Set(constants.BugReportIncludeNSFlagName, "dummy,verrazzano-install") 342 assert.NoError(t, err) 343 err = cmd.Execute() 344 if err != nil { 345 assert.Error(t, err) 346 } 347 348 errBuf, err := os.ReadFile(stderrFile.Name()) 349 assert.NoError(t, err) 350 assert.NotContains(t, string(errBuf), "Verrazzano is not installed") 351 assert.FileExists(t, bugRepFile) 352 } 353 354 // getClientWithWatch returns a client containing all VPO objects 355 func getClientWithWatch() client.WithWatch { 356 return fake.NewClientBuilder().WithScheme(pkghelper.NewScheme()).WithObjects(getVpoObjects()[1:]...).Build() 357 } 358 359 // getClientWithVZWatch returns a client containing all VPO objects and the Verrazzano CR 360 func getClientWithVZWatch() client.WithWatch { 361 return fake.NewClientBuilder().WithScheme(pkghelper.NewScheme()).WithObjects(getVpoObjects()...).Build() 362 } 363 364 func getVpoObjects() []client.Object { 365 return []client.Object{ 366 &v1beta1.Verrazzano{ 367 ObjectMeta: metav1.ObjectMeta{ 368 Namespace: "default", 369 Name: "verrazzano", 370 }, 371 Spec: v1beta1.VerrazzanoSpec{ 372 Profile: v1beta1.Dev, 373 }, 374 }, 375 &corev1.Pod{ 376 ObjectMeta: metav1.ObjectMeta{ 377 Namespace: vzconstants.VerrazzanoInstallNamespace, 378 Name: constants.VerrazzanoPlatformOperator, 379 Labels: map[string]string{ 380 "app": constants.VerrazzanoPlatformOperator, 381 "pod-template-hash": "45f78ffddd", 382 }, 383 }, 384 }, 385 &appsv1.Deployment{ 386 ObjectMeta: metav1.ObjectMeta{ 387 Namespace: vzconstants.VerrazzanoInstallNamespace, 388 Name: constants.VerrazzanoPlatformOperator, 389 }, 390 Spec: appsv1.DeploymentSpec{ 391 Selector: &metav1.LabelSelector{ 392 MatchLabels: map[string]string{"app": constants.VerrazzanoPlatformOperator}, 393 }, 394 }, 395 Status: appsv1.DeploymentStatus{ 396 AvailableReplicas: 1, 397 UpdatedReplicas: 1, 398 }, 399 }, 400 &appsv1.ReplicaSet{ 401 ObjectMeta: metav1.ObjectMeta{ 402 Namespace: vzconstants.VerrazzanoInstallNamespace, 403 Name: fmt.Sprintf("%s-45f78ffddd", constants.VerrazzanoPlatformOperator), 404 Annotations: map[string]string{ 405 "deployment.kubernetes.io/revision": "1", 406 }, 407 }, 408 }, 409 } 410 } 411 412 // getInvalidClient returns an invalid client 413 func getInvalidClient() client.WithWatch { 414 testObj := &corev1.Pod{ 415 ObjectMeta: metav1.ObjectMeta{ 416 Namespace: "testnamespace", 417 Name: "testpod", 418 Labels: map[string]string{ 419 "app": "test-app", 420 "pod-template-hash": "56f78ddcfd", 421 }, 422 }, 423 } 424 deployment := &appsv1.Deployment{ 425 ObjectMeta: metav1.ObjectMeta{ 426 Namespace: "testnamespace", 427 Name: "testpod", 428 }, 429 Spec: appsv1.DeploymentSpec{ 430 Selector: &metav1.LabelSelector{ 431 MatchLabels: map[string]string{"app": "test-app"}, 432 }, 433 }, 434 } 435 return fake.NewClientBuilder().WithScheme(pkghelper.NewScheme()).WithObjects(testObj, deployment).Build() 436 } 437 438 // cleanupTempDir cleans up the given temp directory after the test run 439 func cleanupTempDir(t *testing.T, dirName string) { 440 if err := os.RemoveAll(dirName); err != nil { 441 t.Fatalf("Remove directory failed: %v", err) 442 } 443 } 444 445 // cleanupTempDir cleans up the given temp file after the test run 446 func cleanupFile(t *testing.T, file *os.File) { 447 if err := file.Close(); err != nil { 448 t.Fatalf("Close file failed: %v", err) 449 } 450 if err := os.Remove(file.Name()); err != nil { 451 t.Fatalf("Close file failed: %v", err) 452 } 453 } 454 455 // createStdTempFiles creates temporary files for stdout and stderr. 456 func createStdTempFiles(t *testing.T) (*os.File, *os.File) { 457 stdoutFile, err := os.CreateTemp("", "tmpstdout") 458 assert.NoError(t, err) 459 460 stderrFile, err := os.CreateTemp("", "tmpstderr") 461 assert.NoError(t, err) 462 463 return stdoutFile, stderrFile 464 } 465 466 // TestBugReportSuccess 467 // GIVEN a CLI bug-report command 468 // WHEN I call cmd.Execute with include logs of additional namespace and duration 469 // THEN expect the command to show the resources captured in the standard output and create the bug report file 470 func TestBugReportSuccessWithDuration(t *testing.T) { 471 cmd := setUpAndVerifyResources(t) 472 473 tmpDir, _ := os.MkdirTemp("", "bug-report") 474 defer cleanupTempDir(t, tmpDir) 475 476 bugRepFile := tmpDir + string(os.PathSeparator) + "bug-report.tgz" 477 setUpGlobalFlags(cmd) 478 err := cmd.PersistentFlags().Set(constants.BugReportFileFlagName, bugRepFile) 479 assert.NoError(t, err) 480 err = cmd.PersistentFlags().Set(constants.BugReportIncludeNSFlagName, "dummy,verrazzano-install,default,test") 481 assert.NoError(t, err) 482 err = cmd.PersistentFlags().Set(constants.VerboseFlag, "true") 483 assert.NoError(t, err) 484 err = cmd.PersistentFlags().Set(constants.BugReportLogFlagName, "true") 485 assert.NoError(t, err) 486 // If invalid time value is given then error is expected 487 err = cmd.PersistentFlags().Set(constants.BugReportTimeFlagName, "3t") 488 assert.Error(t, err) 489 // Valid time value 490 err = cmd.PersistentFlags().Set(constants.BugReportTimeFlagName, "3h") 491 assert.NoError(t, err) 492 err = cmd.Execute() 493 if err != nil { 494 assert.Error(t, err) 495 } 496 assert.NoError(t, err) 497 } 498 499 func setUpGlobalFlags(cmd *cobra.Command) { 500 tempKubeConfigPath, _ := os.CreateTemp(os.TempDir(), testKubeConfig) 501 cmd.Flags().String(constants.GlobalFlagKubeConfig, tempKubeConfigPath.Name(), "") 502 cmd.Flags().String(constants.GlobalFlagContext, testK8sContext, "") 503 } 504 505 func setUpAndVerifyResources(t *testing.T) *cobra.Command { 506 c := getClientWithVZWatch() 507 508 // Verify the vz resource is as expected 509 vz := v1beta1.Verrazzano{} 510 err := c.Get(context.TODO(), types.NamespacedName{Namespace: "default", Name: "verrazzano"}, &vz) 511 assert.NoError(t, err) 512 513 stdoutFile, stderrFile := createStdTempFiles(t) 514 defer os.Remove(stdoutFile.Name()) 515 defer os.Remove(stderrFile.Name()) 516 rc := setupFakeDynamicClient(c, genericclioptions.IOStreams{In: os.Stdin, Out: stdoutFile, ErrOut: stderrFile}) 517 518 cmd := NewCmdBugReport(rc) 519 assert.NotNil(t, cmd) 520 521 return cmd 522 } 523 524 func setupFakeDynamicClient(c client.WithWatch, ioStreams genericclioptions.IOStreams) *helpers.FakeRootCmdContext { 525 rc := helpers.NewFakeRootCmdContext(ioStreams) 526 rc.SetClient(c) 527 rc.SetDynamicClient(dynfake.NewSimpleDynamicClient(pkghelper.GetScheme())) 528 return rc 529 }