diff --git a/changelogs/unreleased/6497-blackpiglet b/changelogs/unreleased/6497-blackpiglet new file mode 100644 index 0000000000..30337ae92b --- /dev/null +++ b/changelogs/unreleased/6497-blackpiglet @@ -0,0 +1 @@ +Remove dependency of the legacy client code from pkg/cmd directory part 2 \ No newline at end of file diff --git a/pkg/client/factory_test.go b/pkg/client/factory_test.go index e96b2b0449..547be6bff6 100644 --- a/pkg/client/factory_test.go +++ b/pkg/client/factory_test.go @@ -141,6 +141,10 @@ func TestFactory(t *testing.T) { kubebuilderClient, e := f.KubebuilderClient() assert.Contains(t, e.Error(), fmt.Sprintf("Get \"%s/api?timeout=", test.expectedHost)) assert.Nil(t, kubebuilderClient) + + kbClientWithWatch, e := f.KubebuilderWatchClient() + assert.Contains(t, e.Error(), fmt.Sprintf("Get \"%s/api?timeout=", test.expectedHost)) + assert.Nil(t, kbClientWithWatch) }) } } diff --git a/pkg/cmd/cli/backup/download_test.go b/pkg/cmd/cli/backup/download_test.go index 3a28b73f76..a709a814b8 100644 --- a/pkg/cmd/cli/backup/download_test.go +++ b/pkg/cmd/cli/backup/download_test.go @@ -27,14 +27,12 @@ import ( flag "github.com/spf13/pflag" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "sigs.k8s.io/controller-runtime/pkg/client/fake" "github.com/vmware-tanzu/velero/pkg/builder" + factorymocks "github.com/vmware-tanzu/velero/pkg/client/mocks" cmdtest "github.com/vmware-tanzu/velero/pkg/cmd/test" - "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/scheme" + velerotest "github.com/vmware-tanzu/velero/pkg/test" veleroexec "github.com/vmware-tanzu/velero/pkg/util/exec" - - factorymocks "github.com/vmware-tanzu/velero/pkg/client/mocks" ) func TestNewDownloadCommand(t *testing.T) { @@ -42,7 +40,7 @@ func TestNewDownloadCommand(t *testing.T) { f := &factorymocks.Factory{} backupName := "backup-1" - kbclient := fake.NewClientBuilder().WithScheme(scheme.Scheme).Build() + kbclient := velerotest.NewFakeControllerRuntimeClient(t) err := kbclient.Create(context.Background(), builder.ForBackup(cmdtest.VeleroNameSpace, backupName).Result()) require.NoError(t, err) err = kbclient.Create(context.Background(), builder.ForBackup(cmdtest.VeleroNameSpace, "bk-to-be-download").Result()) diff --git a/pkg/cmd/cli/backup/get.go b/pkg/cmd/cli/backup/get.go index f5ccc07e10..159fac30dd 100644 --- a/pkg/cmd/cli/backup/get.go +++ b/pkg/cmd/cli/backup/get.go @@ -21,6 +21,8 @@ import ( "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + kbclient "sigs.k8s.io/controller-runtime/pkg/client" api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" @@ -38,19 +40,24 @@ func NewGetCommand(f client.Factory, use string) *cobra.Command { err := output.ValidateFlags(c) cmd.CheckError(err) - veleroClient, err := f.Client() + kbClient, err := f.KubebuilderClient() cmd.CheckError(err) - var backups *api.BackupList + backups := new(api.BackupList) if len(args) > 0 { - backups = new(api.BackupList) for _, name := range args { - backup, err := veleroClient.VeleroV1().Backups(f.Namespace()).Get(context.TODO(), name, metav1.GetOptions{}) + backup := new(api.Backup) + err := kbClient.Get(context.TODO(), kbclient.ObjectKey{Namespace: f.Namespace(), Name: name}, backup) cmd.CheckError(err) backups.Items = append(backups.Items, *backup) } } else { - backups, err = veleroClient.VeleroV1().Backups(f.Namespace()).List(context.TODO(), listOptions) + parsedSelector, err := labels.Parse(listOptions.LabelSelector) + cmd.CheckError(err) + err = kbClient.List(context.TODO(), backups, &kbclient.ListOptions{ + LabelSelector: parsedSelector, + Namespace: f.Namespace(), + }) cmd.CheckError(err) } diff --git a/pkg/cmd/cli/backup/get_test.go b/pkg/cmd/cli/backup/get_test.go index fbbeb91913..af37953522 100644 --- a/pkg/cmd/cli/backup/get_test.go +++ b/pkg/cmd/cli/backup/get_test.go @@ -17,6 +17,7 @@ limitations under the License. package backup import ( + "context" "fmt" "os" "os/exec" @@ -24,15 +25,14 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + kbclient "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/vmware-tanzu/velero/pkg/builder" + factorymocks "github.com/vmware-tanzu/velero/pkg/client/mocks" cmdtest "github.com/vmware-tanzu/velero/pkg/cmd/test" + velerotest "github.com/vmware-tanzu/velero/pkg/test" veleroexec "github.com/vmware-tanzu/velero/pkg/util/exec" - - velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" - factorymocks "github.com/vmware-tanzu/velero/pkg/client/mocks" - versionedmocks "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/mocks" - velerov1mocks "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1/mocks" ) func TestNewGetCommand(t *testing.T) { @@ -41,18 +41,16 @@ func TestNewGetCommand(t *testing.T) { // create a factory f := &factorymocks.Factory{} - backups := &velerov1mocks.BackupInterface{} - veleroV1 := &velerov1mocks.VeleroV1Interface{} - client := &versionedmocks.Interface{} - bk := &velerov1api.Backup{} - bkList := &velerov1api.BackupList{} + client := velerotest.NewFakeControllerRuntimeClient(t) - backups.On("List", mock.Anything, mock.Anything).Return(bkList, nil) - backups.On("Get", mock.Anything, mock.Anything, mock.Anything).Return(bk, nil) - veleroV1.On("Backups", mock.Anything).Return(backups, nil) - client.On("VeleroV1").Return(veleroV1, nil) - f.On("Client").Return(client, nil) - f.On("Namespace").Return(mock.Anything) + for _, backupName := range args { + backup := builder.ForBackup(cmdtest.VeleroNameSpace, backupName).ObjectMeta(builder.WithLabels("abc", "abc")).Result() + err := client.Create(context.Background(), backup, &kbclient.CreateOptions{}) + require.NoError(t, err) + } + + f.On("KubebuilderClient").Return(client, nil) + f.On("Namespace").Return(cmdtest.VeleroNameSpace) // create command c := NewGetCommand(f, "velero backup get") @@ -69,6 +67,28 @@ func TestNewGetCommand(t *testing.T) { cmd := exec.Command(os.Args[0], []string{"-test.run=TestNewGetCommand"}...) cmd.Env = append(os.Environ(), fmt.Sprintf("%s=1", cmdtest.CaptureFlag)) stdout, _, err := veleroexec.RunCommand(cmd) + require.NoError(t, err) + + if err == nil { + output := strings.Split(stdout, "\n") + i := 0 + for _, line := range output { + if strings.Contains(line, "New") { + i++ + } + } + assert.Equal(t, len(args), i) + } + + d := NewGetCommand(f, "velero backup get") + c.SetArgs([]string{"-l", "abc=abc"}) + e = d.Execute() + assert.NoError(t, e) + + cmd = exec.Command(os.Args[0], []string{"-test.run=TestNewGetCommand"}...) + cmd.Env = append(os.Environ(), fmt.Sprintf("%s=1", cmdtest.CaptureFlag)) + stdout, _, err = veleroexec.RunCommand(cmd) + require.NoError(t, err) if err == nil { output := strings.Split(stdout, "\n") @@ -79,7 +99,5 @@ func TestNewGetCommand(t *testing.T) { } } assert.Equal(t, len(args), i) - return } - t.Fatalf("process ran with err %v, want backups by get()", err) } diff --git a/pkg/cmd/cli/backup/logs.go b/pkg/cmd/cli/backup/logs.go index 95c2387a31..0655eaa15f 100644 --- a/pkg/cmd/cli/backup/logs.go +++ b/pkg/cmd/cli/backup/logs.go @@ -23,8 +23,9 @@ import ( "time" "github.com/spf13/cobra" + "github.com/spf13/pflag" apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kbclient "sigs.k8s.io/controller-runtime/pkg/client" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" @@ -32,51 +33,84 @@ import ( "github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest" ) -func NewLogsCommand(f client.Factory) *cobra.Command { +type LogsOptions struct { + Timeout time.Duration + InsecureSkipTLSVerify bool + CaCertFile string + Client kbclient.Client + BackupName string +} + +func NewLogsOptions() LogsOptions { config, err := client.LoadConfig() if err != nil { fmt.Fprintf(os.Stderr, "WARNING: Error reading config file: %v\n", err) } - timeout := time.Minute - insecureSkipTLSVerify := false - caCertFile := config.CACertFile() + return LogsOptions{ + Timeout: time.Minute, + InsecureSkipTLSVerify: false, + CaCertFile: config.CACertFile(), + } +} + +func (l *LogsOptions) BindFlags(flags *pflag.FlagSet) { + flags.DurationVar(&l.Timeout, "timeout", l.Timeout, "How long to wait to receive logs.") + flags.BoolVar(&l.InsecureSkipTLSVerify, "insecure-skip-tls-verify", l.InsecureSkipTLSVerify, "If true, the object store's TLS certificate will not be checked for validity. This is insecure and susceptible to man-in-the-middle attacks. Not recommended for production.") + flags.StringVar(&l.CaCertFile, "cacert", l.CaCertFile, "Path to a certificate bundle to use when verifying TLS connections.") +} + +func (l *LogsOptions) Run(c *cobra.Command, f client.Factory) error { + backup := new(velerov1api.Backup) + err := l.Client.Get(context.TODO(), kbclient.ObjectKey{Namespace: f.Namespace(), Name: l.BackupName}, backup) + if apierrors.IsNotFound(err) { + return fmt.Errorf("backup %q does not exist", l.BackupName) + } else if err != nil { + return fmt.Errorf("error checking for backup %q: %v", l.BackupName, err) + } + + switch backup.Status.Phase { + case velerov1api.BackupPhaseCompleted, velerov1api.BackupPhasePartiallyFailed, velerov1api.BackupPhaseFailed, velerov1api.BackupPhaseWaitingForPluginOperations, velerov1api.BackupPhaseWaitingForPluginOperationsPartiallyFailed: + // terminal and waiting for plugin operations phases, do nothing. + default: + return fmt.Errorf("logs for backup %q are not available until it's finished processing, please wait "+ + "until the backup has a phase of Completed or Failed and try again", l.BackupName) + } + + err = downloadrequest.Stream(context.Background(), l.Client, f.Namespace(), l.BackupName, velerov1api.DownloadTargetKindBackupLog, os.Stdout, l.Timeout, l.InsecureSkipTLSVerify, l.CaCertFile) + return err +} + +func (l *LogsOptions) Complete(args []string, f client.Factory) error { + if len(args) > 0 { + l.BackupName = args[0] + } + + kbClient, err := f.KubebuilderClient() + if err != nil { + return err + } + l.Client = kbClient + return nil +} + +func NewLogsCommand(f client.Factory) *cobra.Command { + l := NewLogsOptions() c := &cobra.Command{ Use: "logs BACKUP", Short: "Get backup logs", Args: cobra.ExactArgs(1), Run: func(c *cobra.Command, args []string) { - backupName := args[0] - - veleroClient, err := f.Client() + err := l.Complete(args, f) cmd.CheckError(err) - kbClient, err := f.KubebuilderClient() - cmd.CheckError(err) - - backup, err := veleroClient.VeleroV1().Backups(f.Namespace()).Get(context.TODO(), backupName, metav1.GetOptions{}) - if apierrors.IsNotFound(err) { - cmd.Exit("Backup %q does not exist.", backupName) - } else if err != nil { - cmd.Exit("Error checking for backup %q: %v", backupName, err) - } - - switch backup.Status.Phase { - case velerov1api.BackupPhaseCompleted, velerov1api.BackupPhasePartiallyFailed, velerov1api.BackupPhaseFailed, velerov1api.BackupPhaseWaitingForPluginOperations, velerov1api.BackupPhaseWaitingForPluginOperationsPartiallyFailed: - // terminal and waiting for plugin operations phases, do nothing. - default: - cmd.Exit("Logs for backup %q are not available until it's finished processing. Please wait "+ - "until the backup has a phase of Completed or Failed and try again.", backupName) - } - - err = downloadrequest.Stream(context.Background(), kbClient, f.Namespace(), backupName, velerov1api.DownloadTargetKindBackupLog, os.Stdout, timeout, insecureSkipTLSVerify, caCertFile) + err = l.Run(c, f) cmd.CheckError(err) }, } - c.Flags().DurationVar(&timeout, "timeout", timeout, "How long to wait to receive logs.") - c.Flags().BoolVar(&insecureSkipTLSVerify, "insecure-skip-tls-verify", insecureSkipTLSVerify, "If true, the object store's TLS certificate will not be checked for validity. This is insecure and susceptible to man-in-the-middle attacks. Not recommended for production.") - c.Flags().StringVar(&caCertFile, "cacert", caCertFile, "Path to a certificate bundle to use when verifying TLS connections.") + l.BindFlags(c.Flags()) + return c } diff --git a/pkg/cmd/cli/backup/logs_test.go b/pkg/cmd/cli/backup/logs_test.go index d4b98b8847..9bb95a7a8d 100644 --- a/pkg/cmd/cli/backup/logs_test.go +++ b/pkg/cmd/cli/backup/logs_test.go @@ -17,62 +17,130 @@ limitations under the License. package backup import ( + "context" "fmt" - "os" - "os/exec" + "strconv" "testing" + "time" + flag "github.com/spf13/pflag" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "sigs.k8s.io/controller-runtime/pkg/client/fake" + "github.com/stretchr/testify/require" + kbclient "sigs.k8s.io/controller-runtime/pkg/client" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/builder" factorymocks "github.com/vmware-tanzu/velero/pkg/client/mocks" cmdtest "github.com/vmware-tanzu/velero/pkg/cmd/test" - versionedmocks "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/mocks" - "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/scheme" - velerov1mocks "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1/mocks" - veleroexec "github.com/vmware-tanzu/velero/pkg/util/exec" + velerotest "github.com/vmware-tanzu/velero/pkg/test" ) func TestNewLogsCommand(t *testing.T) { - backupName := "bk-logs-1" - - // create a factory - f := &factorymocks.Factory{} - - backups := &velerov1mocks.BackupInterface{} - veleroV1 := &velerov1mocks.VeleroV1Interface{} - client := &versionedmocks.Interface{} - bk := &velerov1api.Backup{} - bkList := &velerov1api.BackupList{} - kbclient := fake.NewClientBuilder().WithScheme(scheme.Scheme).Build() - - backups.On("List", mock.Anything, mock.Anything).Return(bkList, nil) - backups.On("Get", mock.Anything, mock.Anything, mock.Anything).Return(bk, nil) - veleroV1.On("Backups", mock.Anything).Return(backups, nil) - client.On("VeleroV1").Return(veleroV1, nil) - f.On("Client").Return(client, nil) - f.On("Namespace").Return(mock.Anything) - f.On("KubebuilderClient").Return(kbclient, nil) - - c := NewLogsCommand(f) - assert.Equal(t, "Get backup logs", c.Short) - - if os.Getenv(cmdtest.CaptureFlag) == "1" { - c.SetArgs([]string{backupName}) - e := c.Execute() - assert.NoError(t, e) - return - } - - cmd := exec.Command(os.Args[0], []string{"-test.run=TestNewLogsCommand"}...) - cmd.Env = append(os.Environ(), fmt.Sprintf("%s=1", cmdtest.CaptureFlag)) - _, stderr, err := veleroexec.RunCommand(cmd) - - if err != nil { - assert.Contains(t, stderr, fmt.Sprintf("Logs for backup \"%s\" are not available until it's finished processing", backupName)) - return - } - t.Fatalf("process ran with err %v, want backup delete successfully", err) + t.Run("Flag test", func(t *testing.T) { + l := NewLogsOptions() + flags := new(flag.FlagSet) + l.BindFlags(flags) + + timeout := "1m0s" + insecureSkipTLSVerify := "true" + caCertFile := "testing" + + flags.Parse([]string{"--timeout", timeout}) + flags.Parse([]string{"--insecure-skip-tls-verify", insecureSkipTLSVerify}) + flags.Parse([]string{"--cacert", caCertFile}) + + require.Equal(t, timeout, l.Timeout.String()) + require.Equal(t, insecureSkipTLSVerify, strconv.FormatBool(l.InsecureSkipTLSVerify)) + require.Equal(t, caCertFile, l.CaCertFile) + }) + + t.Run("Backup not complete test", func(t *testing.T) { + backupName := "bk-logs-1" + + // create a factory + f := &factorymocks.Factory{} + + kbClient := velerotest.NewFakeControllerRuntimeClient(t) + backup := builder.ForBackup(cmdtest.VeleroNameSpace, backupName).Result() + err := kbClient.Create(context.Background(), backup, &kbclient.CreateOptions{}) + require.NoError(t, err) + + f.On("Namespace").Return(cmdtest.VeleroNameSpace) + f.On("KubebuilderClient").Return(kbClient, nil) + + c := NewLogsCommand(f) + assert.Equal(t, "Get backup logs", c.Short) + + l := NewLogsOptions() + flags := new(flag.FlagSet) + l.BindFlags(flags) + err = l.Complete([]string{backupName}, f) + require.NoError(t, err) + + err = l.Run(c, f) + require.Error(t, err) + require.Contains(t, err.Error(), fmt.Sprintf("logs for backup \"%s\" are not available until it's finished processing", backupName)) + }) + + t.Run("Backup not exist test", func(t *testing.T) { + backupName := "not-exist" + // create a factory + f := &factorymocks.Factory{} + + kbClient := velerotest.NewFakeControllerRuntimeClient(t) + + f.On("Namespace").Return(cmdtest.VeleroNameSpace) + f.On("KubebuilderClient").Return(kbClient, nil) + + c := NewLogsCommand(f) + assert.Equal(t, "Get backup logs", c.Short) + + l := NewLogsOptions() + flags := new(flag.FlagSet) + l.BindFlags(flags) + err := l.Complete([]string{backupName}, f) + require.NoError(t, err) + + err = l.Run(c, f) + require.Error(t, err) + + require.Equal(t, fmt.Sprintf("backup \"%s\" does not exist", backupName), err.Error()) + }) + + t.Run("Normal backup log test", func(t *testing.T) { + backupName := "bk-logs-1" + + // create a factory + f := &factorymocks.Factory{} + + kbClient := velerotest.NewFakeControllerRuntimeClient(t) + backup := builder.ForBackup(cmdtest.VeleroNameSpace, backupName).Phase(velerov1api.BackupPhaseCompleted).Result() + err := kbClient.Create(context.Background(), backup, &kbclient.CreateOptions{}) + require.NoError(t, err) + + f.On("Namespace").Return(cmdtest.VeleroNameSpace) + f.On("KubebuilderClient").Return(kbClient, nil) + + c := NewLogsCommand(f) + assert.Equal(t, "Get backup logs", c.Short) + + l := NewLogsOptions() + flags := new(flag.FlagSet) + l.BindFlags(flags) + err = l.Complete([]string{backupName}, f) + require.NoError(t, err) + + timeout := time.After(3 * time.Second) + done := make(chan bool) + go func() { + err = l.Run(c, f) + require.Error(t, err) + }() + + select { + case <-timeout: + t.Skip("Test didn't finish in time, because BSL is not in Available state.") + case <-done: + } + }) } diff --git a/pkg/cmd/cli/backuplocation/create_test.go b/pkg/cmd/cli/backuplocation/create_test.go index 34293fb889..dc2cb1aae0 100644 --- a/pkg/cmd/cli/backuplocation/create_test.go +++ b/pkg/cmd/cli/backuplocation/create_test.go @@ -29,16 +29,9 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - veleroflag "github.com/vmware-tanzu/velero/pkg/cmd/util/flag" - "github.com/vmware-tanzu/velero/pkg/test" - - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" factorymocks "github.com/vmware-tanzu/velero/pkg/client/mocks" - versionedmocks "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/mocks" - "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/scheme" - velerov1mocks "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1/mocks" + veleroflag "github.com/vmware-tanzu/velero/pkg/cmd/util/flag" + velerotest "github.com/vmware-tanzu/velero/pkg/test" ) func TestBuildBackupStorageLocationSetsNamespace(t *testing.T) { @@ -149,16 +142,7 @@ func TestCreateCommand_Run(t *testing.T) { args := []string{name, "arg2"} - backups := &velerov1mocks.BackupInterface{} - veleroV1 := &velerov1mocks.VeleroV1Interface{} - client := &versionedmocks.Interface{} - bk := &velerov1api.Backup{} - kbclient := fake.NewClientBuilder().WithScheme(scheme.Scheme).Build() - - backups.On("Create", mock.Anything, mock.Anything, mock.Anything).Return(bk, nil) - veleroV1.On("Backups", mock.Anything).Return(backups, nil) - client.On("VeleroV1").Return(veleroV1, nil) - f.On("Client").Return(client, nil) + kbclient := velerotest.NewFakeControllerRuntimeClient(t) f.On("Namespace").Return(mock.Anything) f.On("KubebuilderClient").Return(kbclient, nil) @@ -179,7 +163,7 @@ func TestCreateCommand_Run(t *testing.T) { assert.Equal(t, backupSyncPeriod, o.BackupSyncPeriod.String()) assert.Equal(t, validationFrequency, o.ValidationFrequency.String()) assert.Equal(t, true, reflect.DeepEqual(bslConfig, o.Config)) - assert.Equal(t, true, test.CompareSlice(strings.Split(labels, ","), strings.Split(o.Labels.String(), ","))) + assert.Equal(t, true, velerotest.CompareSlice(strings.Split(labels, ","), strings.Split(o.Labels.String(), ","))) assert.Equal(t, caCertFile, o.CACertFile) assert.Equal(t, accessMode, o.AccessMode.String()) diff --git a/pkg/cmd/cli/backuplocation/set_test.go b/pkg/cmd/cli/backuplocation/set_test.go index d97327126c..b57f6a3208 100644 --- a/pkg/cmd/cli/backuplocation/set_test.go +++ b/pkg/cmd/cli/backuplocation/set_test.go @@ -27,16 +27,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + factorymocks "github.com/vmware-tanzu/velero/pkg/client/mocks" cmdtest "github.com/vmware-tanzu/velero/pkg/cmd/test" veleroflag "github.com/vmware-tanzu/velero/pkg/cmd/util/flag" - - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" - factorymocks "github.com/vmware-tanzu/velero/pkg/client/mocks" - versionedmocks "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/mocks" - "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/scheme" - velerov1mocks "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1/mocks" + velerotest "github.com/vmware-tanzu/velero/pkg/test" veleroexec "github.com/vmware-tanzu/velero/pkg/util/exec" ) @@ -44,16 +38,9 @@ func TestNewSetCommand(t *testing.T) { backupName := "arg2" // create a config for factory f := &factorymocks.Factory{} - backups := &velerov1mocks.BackupInterface{} - veleroV1 := &velerov1mocks.VeleroV1Interface{} - client := &versionedmocks.Interface{} - bk := &velerov1api.Backup{} - kbclient := fake.NewClientBuilder().WithScheme(scheme.Scheme).Build() - - backups.On("Create", mock.Anything, mock.Anything, mock.Anything).Return(bk, nil) - veleroV1.On("Backups", mock.Anything).Return(backups, nil) - client.On("VeleroV1").Return(veleroV1, nil) - f.On("Client").Return(client, nil) + + kbclient := velerotest.NewFakeControllerRuntimeClient(t) + f.On("Namespace").Return(mock.Anything) f.On("KubebuilderClient").Return(kbclient, nil) @@ -97,16 +84,9 @@ func TestSetCommand_Execute(t *testing.T) { if os.Getenv(cmdtest.CaptureFlag) == "1" { // create a config for factory f := &factorymocks.Factory{} - backups := &velerov1mocks.BackupInterface{} - veleroV1 := &velerov1mocks.VeleroV1Interface{} - client := &versionedmocks.Interface{} - bk := &velerov1api.Backup{} - kbclient := fake.NewClientBuilder().WithScheme(scheme.Scheme).Build() - - backups.On("Create", mock.Anything, mock.Anything, mock.Anything).Return(bk, nil) - veleroV1.On("Backups", mock.Anything).Return(backups, nil) - client.On("VeleroV1").Return(veleroV1, nil) - f.On("Client").Return(client, nil) + + kbclient := velerotest.NewFakeControllerRuntimeClient(t) + f.On("Namespace").Return(mock.Anything) f.On("KubebuilderClient").Return(kbclient, nil) diff --git a/pkg/cmd/cli/restore/create.go b/pkg/cmd/cli/restore/create.go index 44db8eb8e1..39219a53ff 100644 --- a/pkg/cmd/cli/restore/create.go +++ b/pkg/cmd/cli/restore/create.go @@ -26,16 +26,17 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/tools/cache" + kbclient "sigs.k8s.io/controller-runtime/pkg/client" api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "github.com/vmware-tanzu/velero/pkg/client" "github.com/vmware-tanzu/velero/pkg/cmd" "github.com/vmware-tanzu/velero/pkg/cmd/util/flag" "github.com/vmware-tanzu/velero/pkg/cmd/util/output" - veleroclient "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned" - v1 "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions/velero/v1" "github.com/vmware-tanzu/velero/pkg/util/boolptr" + "github.com/vmware-tanzu/velero/pkg/util/kube" ) func NewCreateCommand(f client.Factory, use string) *cobra.Command { @@ -93,8 +94,7 @@ type CreateOptions struct { Wait bool AllowPartiallyFailed flag.OptionalBool ItemOperationTimeout time.Duration - - client veleroclient.Interface + client kbclient.WithWatch } func NewCreateOptions() *CreateOptions { @@ -153,7 +153,7 @@ func (o *CreateOptions) Complete(args []string, f client.Factory) error { o.RestoreName = fmt.Sprintf("%s-%s", sourceName, time.Now().Format("20060102150405")) } - client, err := f.Client() + client, err := f.KubebuilderWatchClient() if err != nil { return err } @@ -186,15 +186,20 @@ func (o *CreateOptions) Validate(c *cobra.Command, args []string, f client.Facto switch { case o.BackupName != "": - if _, err := o.client.VeleroV1().Backups(f.Namespace()).Get(context.TODO(), o.BackupName, metav1.GetOptions{}); err != nil { + backup := new(api.Backup) + if err := o.client.Get(context.TODO(), kbclient.ObjectKey{Namespace: f.Namespace(), Name: o.BackupName}, backup); err != nil { return err } case o.ScheduleName != "": - backupItems, err := o.client.VeleroV1().Backups(f.Namespace()).List(context.TODO(), metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", api.ScheduleNameLabel, o.ScheduleName)}) + backupList := new(api.BackupList) + err := o.client.List(context.TODO(), backupList, &kbclient.ListOptions{ + LabelSelector: labels.SelectorFromSet(map[string]string{api.ScheduleNameLabel: o.ScheduleName}), + Namespace: f.Namespace(), + }) if err != nil { return err } - if len(backupItems.Items) == 0 { + if len(backupList.Items) == 0 { return errors.Errorf("No backups found for the schedule %s", o.ScheduleName) } } @@ -248,14 +253,18 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error { // PartiallyFailed backup for the provided schedule, and use that specific backup // to restore from. if o.ScheduleName != "" && boolptr.IsSetToTrue(o.AllowPartiallyFailed.Value) { - backups, err := o.client.VeleroV1().Backups(f.Namespace()).List(context.TODO(), metav1.ListOptions{LabelSelector: fmt.Sprintf("%s=%s", api.ScheduleNameLabel, o.ScheduleName)}) + backupList := new(api.BackupList) + err := o.client.List(context.TODO(), backupList, &kbclient.ListOptions{ + LabelSelector: labels.SelectorFromSet(map[string]string{api.ScheduleNameLabel: o.ScheduleName}), + Namespace: f.Namespace(), + }) if err != nil { return err } // if we find a Completed or PartiallyFailed backup for the schedule, restore specifically from that backup. If we don't // find one, proceed as-is -- the Velero server will handle validation. - if backup := mostRecentBackup(backups.Items, api.BackupPhaseCompleted, api.BackupPhasePartiallyFailed); backup != nil { + if backup := mostRecentBackup(backupList.Items, api.BackupPhaseCompleted, api.BackupPhasePartiallyFailed); backup != nil { // TODO(sk): this is kind of a hack -- we should revisit this and probably // move this logic to the server side or otherwise solve this problem. o.BackupName = backup.Name @@ -299,7 +308,6 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error { return err } - var restoreInformer cache.SharedIndexInformer var updates chan *api.Restore if o.Wait { stop := make(chan struct{}) @@ -307,7 +315,12 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error { updates = make(chan *api.Restore) - restoreInformer = v1.NewRestoreInformer(o.client, f.Namespace(), 0, nil) + lw := kube.InternalLW{ + Client: o.client, + Namespace: f.Namespace(), + ObjectList: new(api.RestoreList), + } + restoreInformer := cache.NewSharedInformer(&lw, &api.Restore{}, time.Second) restoreInformer.AddEventHandler( cache.FilteringResourceEventHandler{ @@ -339,7 +352,7 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error { go restoreInformer.Run(stop) } - restore, err := o.client.VeleroV1().Restores(restore.Namespace).Create(context.TODO(), restore, metav1.CreateOptions{}) + err := o.client.Create(context.TODO(), restore, &kbclient.CreateOptions{}) if err != nil { return err } diff --git a/pkg/cmd/cli/restore/create_test.go b/pkg/cmd/cli/restore/create_test.go new file mode 100644 index 0000000000..e62d3d613d --- /dev/null +++ b/pkg/cmd/cli/restore/create_test.go @@ -0,0 +1,174 @@ +/* +Copyright 2020 the Velero contributors. + +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 restore + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/spf13/pflag" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + controllerclient "sigs.k8s.io/controller-runtime/pkg/client" + kbclient "sigs.k8s.io/controller-runtime/pkg/client" + + velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/builder" + factorymocks "github.com/vmware-tanzu/velero/pkg/client/mocks" + cmdtest "github.com/vmware-tanzu/velero/pkg/cmd/test" + velerotest "github.com/vmware-tanzu/velero/pkg/test" +) + +func TestIsResourcePolicyValid(t *testing.T) { + require.True(t, isResourcePolicyValid(string(velerov1api.PolicyTypeNone))) + require.True(t, isResourcePolicyValid(string(velerov1api.PolicyTypeUpdate))) + require.False(t, isResourcePolicyValid("")) +} + +func TestMostRecentBackup(t *testing.T) { + backups := []velerov1api.Backup{ + *builder.ForBackup(cmdtest.VeleroNameSpace, "backup0").StartTimestamp(time.Now().Add(3 * time.Second)).Phase(velerov1api.BackupPhaseDeleting).Result(), + *builder.ForBackup(cmdtest.VeleroNameSpace, "backup1").StartTimestamp(time.Now().Add(time.Second)).Phase(velerov1api.BackupPhaseCompleted).Result(), + *builder.ForBackup(cmdtest.VeleroNameSpace, "backup2").StartTimestamp(time.Now().Add(2 * time.Second)).Phase(velerov1api.BackupPhasePartiallyFailed).Result(), + } + + expectedBackup := builder.ForBackup(cmdtest.VeleroNameSpace, "backup2").StartTimestamp(time.Now().Add(2 * time.Second)).Phase(velerov1api.BackupPhasePartiallyFailed).Result() + + resultBackup := mostRecentBackup(backups, velerov1api.BackupPhaseCompleted, velerov1api.BackupPhasePartiallyFailed) + + require.Equal(t, expectedBackup.Name, resultBackup.Name) +} + +func TestCreateCommand(t *testing.T) { + name := "nameToBeCreated" + args := []string{name} + + t.Run("create a backup create command with full options except fromSchedule and wait, then run by create option", func(t *testing.T) { + + // create a factory + f := &factorymocks.Factory{} + + // create command + cmd := NewCreateCommand(f, "") + require.Equal(t, "Create a restore", cmd.Short) + + backupName := "backup1" + scheduleName := "schedule1" + restoreVolumes := "true" + preserveNodePorts := "true" + labels := "c=foo,b=woo" + includeNamespaces := "app1,app2" + excludeNamespaces := "pod1,pod2,pod3" + existingResourcePolicy := "none" + includeResources := "sc,sts" + excludeResources := "job" + statusIncludeResources := "sc,sts" + statusExcludeResources := "job" + namespaceMappings := "a:b" + selector := "foo=bar" + includeClusterResources := "true" + allowPartiallyFailed := "true" + itemOperationTimeout := "10m0s" + + flags := new(pflag.FlagSet) + o := NewCreateOptions() + o.BindFlags(flags) + + flags.Parse([]string{"--from-backup", backupName}) + flags.Parse([]string{"--from-schedule", scheduleName}) + flags.Parse([]string{"--restore-volumes", restoreVolumes}) + flags.Parse([]string{"--preserve-nodeports", preserveNodePorts}) + flags.Parse([]string{"--labels", labels}) + flags.Parse([]string{"--existing-resource-policy", existingResourcePolicy}) + flags.Parse([]string{"--include-namespaces", includeNamespaces}) + flags.Parse([]string{"--exclude-namespaces", excludeNamespaces}) + flags.Parse([]string{"--include-resources", includeResources}) + flags.Parse([]string{"--exclude-resources", excludeResources}) + flags.Parse([]string{"--status-include-resources", statusIncludeResources}) + flags.Parse([]string{"--status-exclude-resources", statusExcludeResources}) + flags.Parse([]string{"--namespace-mappings", namespaceMappings}) + flags.Parse([]string{"--selector", selector}) + flags.Parse([]string{"--include-cluster-resources", includeClusterResources}) + flags.Parse([]string{"--allow-partially-failed", allowPartiallyFailed}) + flags.Parse([]string{"--item-operation-timeout", itemOperationTimeout}) + + client := velerotest.NewFakeControllerRuntimeClient(t).(kbclient.WithWatch) + + f.On("Namespace").Return(mock.Anything) + f.On("KubebuilderWatchClient").Return(client, nil) + + //Complete + e := o.Complete(args, f) + require.NoError(t, e) + + //Validate + e = o.Validate(cmd, args, f) + require.Contains(t, e.Error(), "either a backup or schedule must be specified, but not both") + + //cmd + e = o.Run(cmd, f) + require.NoError(t, e) + + require.Equal(t, backupName, o.BackupName) + require.Equal(t, scheduleName, o.ScheduleName) + require.Equal(t, restoreVolumes, o.RestoreVolumes.String()) + require.Equal(t, preserveNodePorts, o.PreserveNodePorts.String()) + require.Equal(t, labels, o.Labels.String()) + require.Equal(t, includeNamespaces, o.IncludeNamespaces.String()) + require.Equal(t, excludeNamespaces, o.ExcludeNamespaces.String()) + require.Equal(t, existingResourcePolicy, o.ExistingResourcePolicy) + require.Equal(t, includeResources, o.IncludeResources.String()) + require.Equal(t, excludeResources, o.ExcludeResources.String()) + + require.Equal(t, statusIncludeResources, o.StatusIncludeResources.String()) + require.Equal(t, statusExcludeResources, o.StatusExcludeResources.String()) + require.Equal(t, namespaceMappings, o.NamespaceMappings.String()) + require.Equal(t, selector, o.Selector.String()) + require.Equal(t, includeClusterResources, o.IncludeClusterResources.String()) + require.Equal(t, allowPartiallyFailed, o.AllowPartiallyFailed.String()) + require.Equal(t, itemOperationTimeout, o.ItemOperationTimeout.String()) + + }) + + t.Run("create a restore create from schedule", func(t *testing.T) { + f := &factorymocks.Factory{} + c := NewCreateCommand(f, "") + require.Equal(t, "Create a restore", c.Short) + flags := new(pflag.FlagSet) + o := NewCreateOptions() + o.BindFlags(flags) + + fromSchedule := "schedule-name-1" + flags.Parse([]string{"--from-schedule", fromSchedule}) + + fmt.Printf("debug, restore options: %+v\n", o) + + kbclient := velerotest.NewFakeControllerRuntimeClient(t).(kbclient.WithWatch) + + schedule := builder.ForSchedule(cmdtest.VeleroNameSpace, fromSchedule).Result() + kbclient.Create(context.Background(), schedule, &controllerclient.CreateOptions{}) + + f.On("Namespace").Return(cmdtest.VeleroNameSpace) + f.On("KubebuilderWatchClient").Return(kbclient, nil) + + require.NoError(t, o.Complete(args, f)) + + require.NoError(t, o.Run(c, f)) + }) +} diff --git a/pkg/cmd/cli/restore/restore_test.go b/pkg/cmd/cli/restore/restore_test.go new file mode 100644 index 0000000000..85f4b662a5 --- /dev/null +++ b/pkg/cmd/cli/restore/restore_test.go @@ -0,0 +1,34 @@ +/* +Copyright The Velero Contributors. + +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 restore + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + factorymocks "github.com/vmware-tanzu/velero/pkg/client/mocks" +) + +func TestNewRestoreCommand(t *testing.T) { + // create a factory + f := &factorymocks.Factory{} + + // create command + cmd := NewCommand(f) + assert.Equal(t, "Work with restores", cmd.Short) +} diff --git a/pkg/util/kube/list_watch_test.go b/pkg/util/kube/list_watch_test.go new file mode 100644 index 0000000000..cf7056f5fc --- /dev/null +++ b/pkg/util/kube/list_watch_test.go @@ -0,0 +1,54 @@ +/* +Copyright The Velero Contributors. + +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 kube + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + "k8s.io/client-go/tools/cache" + kbclient "sigs.k8s.io/controller-runtime/pkg/client" + + velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + cmdtest "github.com/vmware-tanzu/velero/pkg/cmd/test" + velerotest "github.com/vmware-tanzu/velero/pkg/test" +) + +func TestInternalLW(t *testing.T) { + stop := make(chan struct{}) + client := velerotest.NewFakeControllerRuntimeClient(t).(kbclient.WithWatch) + lw := InternalLW{ + Client: client, + Namespace: cmdtest.VeleroNameSpace, + ObjectList: new(velerov1api.BackupList), + } + + restoreInformer := cache.NewSharedInformer(&lw, &velerov1api.BackupList{}, time.Second) + go restoreInformer.Run(stop) + + time.Sleep(1 * time.Second) + close(stop) + + backupList := new(velerov1api.BackupList) + err := client.List(context.Background(), backupList) + require.NoError(t, err) + + _, err = client.Watch(context.Background(), backupList) + require.NoError(t, err) +}