package install import ( "bytes" "errors" "os" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" appsv1api "k8s.io/api/apps/v1" corev1api "k8s.io/api/core/v1" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/client/fake" v1crds "github.com/vmware-tanzu/velero/config/crd/v1/crds" "github.com/vmware-tanzu/velero/pkg/test" ) func TestInstall(t *testing.T) { dc := &test.FakeDynamicClient{} dc.On("Create", mock.Anything).Return(&unstructured.Unstructured{}, nil) factory := &test.FakeDynamicFactory{} factory.On("ClientForGroupVersionResource", mock.Anything, mock.Anything, mock.Anything).Return(dc, nil) c := fake.NewClientBuilder().WithObjects( &apiextv1.CustomResourceDefinition{ ObjectMeta: metav1.ObjectMeta{ Name: "backuprepositories.velero.io", }, Status: apiextv1.CustomResourceDefinitionStatus{ Conditions: []apiextv1.CustomResourceDefinitionCondition{ { Type: apiextv1.Established, Status: apiextv1.ConditionTrue, }, { Type: apiextv1.NamesAccepted, Status: apiextv1.ConditionTrue, }, }, }, }, ).Build() resources := &unstructured.UnstructuredList{} require.NoError(t, appendUnstructured(resources, v1crds.CRDs[0])) require.NoError(t, appendUnstructured(resources, Namespace("velero"))) assert.NoError(t, Install(factory, c, resources, os.Stdout, false)) } func Test_crdsAreReady(t *testing.T) { c := fake.NewClientBuilder().WithObjects( &apiextv1beta1.CustomResourceDefinition{ ObjectMeta: metav1.ObjectMeta{ Name: "backuprepositories.velero.io", }, Status: apiextv1beta1.CustomResourceDefinitionStatus{ Conditions: []apiextv1beta1.CustomResourceDefinitionCondition{ { Type: apiextv1beta1.Established, Status: apiextv1beta1.ConditionTrue, }, { Type: apiextv1beta1.NamesAccepted, Status: apiextv1beta1.ConditionTrue, }, }, }, }, ).Build() crd := &apiextv1beta1.CustomResourceDefinition{ TypeMeta: metav1.TypeMeta{ Kind: "CustomResourceDefinition", APIVersion: "v1beta1", }, ObjectMeta: metav1.ObjectMeta{ Name: "backuprepositories.velero.io", }, } obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(crd) require.NoError(t, err) crds := []*unstructured.Unstructured{ { Object: obj, }, } ready, err := crdsAreReady(c, crds) require.NoError(t, err) assert.True(t, ready) } func TestDeploymentIsReady(t *testing.T) { deployment := &appsv1api.Deployment{ Status: appsv1api.DeploymentStatus{ Conditions: []appsv1api.DeploymentCondition{ { Type: appsv1api.DeploymentAvailable, Status: corev1api.ConditionTrue, LastTransitionTime: metav1.NewTime(time.Now().Add(-15 * time.Second)), }, }, }, } obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(deployment) require.NoError(t, err) dc := &test.FakeDynamicClient{} dc.On("Get", mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: obj}, nil) factory := &test.FakeDynamicFactory{} factory.On("ClientForGroupVersionResource", mock.Anything, mock.Anything, mock.Anything).Return(dc, nil) ready, err := DeploymentIsReady(factory, "velero") require.NoError(t, err) assert.True(t, ready) } func TestNodeAgentIsReady(t *testing.T) { daemonset := &appsv1api.DaemonSet{ Status: appsv1api.DaemonSetStatus{ NumberAvailable: 1, DesiredNumberScheduled: 1, }, } obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(daemonset) require.NoError(t, err) dc := &test.FakeDynamicClient{} dc.On("Get", mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: obj}, nil) factory := &test.FakeDynamicFactory{} factory.On("ClientForGroupVersionResource", mock.Anything, mock.Anything, mock.Anything).Return(dc, nil) ready, err := NodeAgentIsReady(factory, "velero") require.NoError(t, err) assert.True(t, ready) } func TestNodeAgentWindowsIsReady(t *testing.T) { daemonset := &appsv1api.DaemonSet{ Status: appsv1api.DaemonSetStatus{ NumberAvailable: 0, DesiredNumberScheduled: 0, }, } obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(daemonset) require.NoError(t, err) dc := &test.FakeDynamicClient{} dc.On("Get", mock.Anything, mock.Anything).Return(&unstructured.Unstructured{Object: obj}, nil) factory := &test.FakeDynamicFactory{} factory.On("ClientForGroupVersionResource", mock.Anything, mock.Anything, mock.Anything).Return(dc, nil) ready, err := NodeAgentWindowsIsReady(factory, "velero") require.NoError(t, err) assert.True(t, ready) } func TestCreateOrApplyResourceError(t *testing.T) { r := &unstructured.Unstructured{ Object: map[string]any{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": map[string]any{ "name": "test-configmap", "namespace": "velero", }, }, } dc := &test.FakeDynamicClient{} expectedErr := errors.New("create error") dc.On("Create", mock.Anything).Return(&unstructured.Unstructured{}, expectedErr) factory := &test.FakeDynamicFactory{} factory.On("ClientForGroupVersionResource", mock.Anything, mock.Anything, mock.Anything).Return(dc, nil) var buf bytes.Buffer err := createOrApplyResource(r, factory, &buf, false) require.Error(t, err) require.Contains(t, err.Error(), expectedErr.Error()) } func TestCreateOrApplyResourceAlreadyExists(t *testing.T) { r := &unstructured.Unstructured{ Object: map[string]any{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": map[string]any{ "name": "test-configmap", "namespace": "velero", }, }, } dc := &test.FakeDynamicClient{} alreadyExistsErr := apierrors.NewAlreadyExists(schema.GroupResource{Resource: "configmaps"}, "test-configmap") // We need to return a non-nil unstructured object even though it's not used dc.On("Create", mock.Anything).Return(&unstructured.Unstructured{}, alreadyExistsErr) factory := &test.FakeDynamicFactory{} factory.On("ClientForGroupVersionResource", mock.Anything, mock.Anything, mock.Anything).Return(dc, nil) var buf bytes.Buffer err := createOrApplyResource(r, factory, &buf, false) require.NoError(t, err) } func TestCreateOrApplyResourceClientError(t *testing.T) { r := &unstructured.Unstructured{ Object: map[string]any{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": map[string]any{ "name": "test-configmap", "namespace": "velero", }, }, } factory := &test.FakeDynamicFactory{} expectedErr := errors.New("client creation error") // Return error from ClientForGroupVersionResource factory.On("ClientForGroupVersionResource", mock.Anything, mock.Anything, mock.Anything).Return(&test.FakeDynamicClient{}, expectedErr) var buf bytes.Buffer err := createOrApplyResource(r, factory, &buf, false) require.Error(t, err) require.Contains(t, err.Error(), expectedErr.Error()) } func TestCreateOrApplyResourceApplyError(t *testing.T) { r := &unstructured.Unstructured{ Object: map[string]any{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": map[string]any{ "name": "test-configmap", "namespace": "velero", }, }, } dc := &test.FakeDynamicClient{} expectedErr := errors.New("apply error") // Mock Apply to return an error dc.On("Apply", mock.Anything, mock.Anything, mock.Anything).Return(&unstructured.Unstructured{}, expectedErr) factory := &test.FakeDynamicFactory{} factory.On("ClientForGroupVersionResource", mock.Anything, mock.Anything, mock.Anything).Return(dc, nil) var buf bytes.Buffer err := createOrApplyResource(r, factory, &buf, true) // true for apply flag to use Apply require.Error(t, err) require.Contains(t, err.Error(), expectedErr.Error()) } func TestInstallErrorAfterCreateClient(t *testing.T) { // Create a test non-CRD resource nonCRDResource := &unstructured.Unstructured{ Object: map[string]any{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": map[string]any{ "name": "test-configmap", }, }, } resources := &unstructured.UnstructuredList{ Items: []unstructured.Unstructured{*nonCRDResource}, } // Mock the factory to return a client that will succeed on ClientForGroupVersionResource // but fail on Create dc := &test.FakeDynamicClient{} expectedErr := errors.New("create error after successful client creation") dc.On("Create", mock.Anything).Return(&unstructured.Unstructured{}, expectedErr) factory := &test.FakeDynamicFactory{} factory.On("ClientForGroupVersionResource", mock.Anything, mock.Anything, mock.Anything).Return(dc, nil) c := fake.NewClientBuilder().Build() var buf bytes.Buffer err := Install(factory, c, resources, &buf, false) require.Error(t, err) require.Contains(t, err.Error(), expectedErr.Error()) } func TestInstallErrorOnCRDResource(t *testing.T) { crdResource := &unstructured.Unstructured{ Object: map[string]any{ "apiVersion": "apiextensions.k8s.io/v1", "kind": "CustomResourceDefinition", "metadata": map[string]any{ "name": "test-crd", }, }, } resources := &unstructured.UnstructuredList{ Items: []unstructured.Unstructured{*crdResource}, } dc := &test.FakeDynamicClient{} expectedErr := errors.New("error creating CRD resource") // We need to return a non-nil unstructured object even though it's not used dc.On("Create", mock.Anything).Return(&unstructured.Unstructured{}, expectedErr) factory := &test.FakeDynamicFactory{} factory.On("ClientForGroupVersionResource", mock.Anything, mock.Anything, mock.Anything).Return(dc, nil) c := fake.NewClientBuilder().Build() var buf bytes.Buffer err := Install(factory, c, resources, &buf, false) require.Error(t, err) require.Contains(t, err.Error(), expectedErr.Error()) } func TestInstallWithApplyFlag(t *testing.T) { // Create a test resource testResource := &unstructured.Unstructured{ Object: map[string]any{ "apiVersion": "v1", "kind": "ConfigMap", "metadata": map[string]any{ "name": "test-configmap", "namespace": "velero", }, "data": map[string]any{ "key1": "value1", }, }, } resources := &unstructured.UnstructuredList{ Items: []unstructured.Unstructured{*testResource}, } // Test case 1: Without apply flag (create) { dc := &test.FakeDynamicClient{} // Expect Create to be called dc.On("Create", mock.Anything).Return(testResource, nil) // Apply should not be called factory := &test.FakeDynamicFactory{} factory.On("ClientForGroupVersionResource", mock.Anything, mock.Anything, mock.Anything).Return(dc, nil) c := fake.NewClientBuilder().Build() err := Install(factory, c, resources, os.Stdout, false) require.NoError(t, err) // Verify that Create was called and Apply was not dc.AssertCalled(t, "Create", mock.Anything) dc.AssertNotCalled(t, "Apply", mock.Anything, mock.Anything, mock.Anything) } // Test case 2: With apply flag { dc := &test.FakeDynamicClient{} // Create should not be called // Expect Apply to be called dc.On("Apply", mock.Anything, mock.Anything, mock.Anything).Return(testResource, nil) factory := &test.FakeDynamicFactory{} factory.On("ClientForGroupVersionResource", mock.Anything, mock.Anything, mock.Anything).Return(dc, nil) c := fake.NewClientBuilder().Build() err := Install(factory, c, resources, os.Stdout, true) require.NoError(t, err) // Verify that Apply was called and Create was not dc.AssertCalled(t, "Apply", mock.Anything, mock.Anything, mock.Anything) dc.AssertNotCalled(t, "Create", mock.Anything) } }