github.com/verrazzano/verrazzano@v1.7.0/cluster-operator/controllers/vmc/push_manifest_objects_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 vmc
     5  
     6  import (
     7  	"bytes"
     8  	"io"
     9  	"net/http"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/golang/mock/gomock"
    14  	asserts "github.com/stretchr/testify/assert"
    15  	"github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1"
    16  	pkgconst "github.com/verrazzano/verrazzano/pkg/constants"
    17  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    18  	"github.com/verrazzano/verrazzano/pkg/rancherutil"
    19  	"github.com/verrazzano/verrazzano/pkg/test/mockmatchers"
    20  	"github.com/verrazzano/verrazzano/platform-operator/constants"
    21  	"github.com/verrazzano/verrazzano/platform-operator/mocks"
    22  	corev1 "k8s.io/api/core/v1"
    23  	networkv1 "k8s.io/api/networking/v1"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/util/wait"
    26  	"sigs.k8s.io/controller-runtime/pkg/client"
    27  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    28  )
    29  
    30  // TestPushManifestObjects tests the push of manifest objects to a managed cluster
    31  // GIVEN a call to push manifest objects
    32  //
    33  //	WHEN the status of the VMC does not contain the condition update
    34  //	THEN the manifest objects should get pushed to the managed cluster
    35  func TestPushManifestObjects(t *testing.T) {
    36  	a := asserts.New(t)
    37  	c := generateClientObjects()
    38  
    39  	savedRancherHTTPClient := rancherutil.RancherHTTPClient
    40  	defer func() {
    41  		rancherutil.RancherHTTPClient = savedRancherHTTPClient
    42  	}()
    43  
    44  	savedRetry := rancherutil.DefaultRetry
    45  	defer func() {
    46  		rancherutil.DefaultRetry = savedRetry
    47  	}()
    48  	rancherutil.DefaultRetry = wait.Backoff{
    49  		Steps:    1,
    50  		Duration: 1 * time.Millisecond,
    51  		Factor:   1.0,
    52  		Jitter:   0.1,
    53  	}
    54  
    55  	vmc := &v1alpha1.VerrazzanoManagedCluster{
    56  		ObjectMeta: metav1.ObjectMeta{
    57  			Namespace: rancherNamespace,
    58  			Name:      "cluster",
    59  		},
    60  		Status: v1alpha1.VerrazzanoManagedClusterStatus{
    61  			RancherRegistration: v1alpha1.RancherRegistration{
    62  				ClusterID: "cluster-id",
    63  			},
    64  		},
    65  	}
    66  	r := &VerrazzanoManagedClusterReconciler{
    67  		Client: c,
    68  		log:    vzlog.DefaultLogger(),
    69  	}
    70  
    71  	statusTrueVMC := vmc.DeepCopy()
    72  	statusTrueVMC.Status.Conditions = append(statusTrueVMC.Status.Conditions, v1alpha1.Condition{
    73  		Type:   v1alpha1.ConditionManifestPushed,
    74  		Status: corev1.ConditionTrue,
    75  	})
    76  
    77  	mocker := gomock.NewController(t)
    78  
    79  	tests := []struct {
    80  		name    string
    81  		vmc     *v1alpha1.VerrazzanoManagedCluster
    82  		updated bool
    83  		mock    *mocks.MockRequestSender
    84  	}{
    85  		{
    86  			name:    "test not active",
    87  			vmc:     vmc,
    88  			updated: false,
    89  			mock:    addInactiveClusterMock(mocks.NewMockRequestSender(mocker), vmc.Status.RancherRegistration.ClusterID),
    90  		},
    91  		{
    92  			name:    "test no verrazzano-system namespace",
    93  			vmc:     vmc,
    94  			updated: false,
    95  			mock:    addNoVerrazzanoSystemNSMock(mocks.NewMockRequestSender(mocker), vmc.Status.RancherRegistration.ClusterID),
    96  		},
    97  		{
    98  			name:    "test active",
    99  			vmc:     vmc,
   100  			updated: true,
   101  			mock:    addActiveClusterMock(mocks.NewMockRequestSender(mocker), a, vmc, r, vmc.Status.RancherRegistration.ClusterID),
   102  		},
   103  	}
   104  	for _, tt := range tests {
   105  		t.Run(tt.name, func(t *testing.T) {
   106  			// clear any cached user auth tokens when the test completes
   107  			defer rancherutil.DeleteStoredTokens()
   108  
   109  			rancherutil.RancherHTTPClient = tt.mock
   110  			updated, err := r.pushManifestObjects(tt.vmc)
   111  			a.Equal(tt.updated, updated)
   112  			a.NoError(err)
   113  		})
   114  	}
   115  }
   116  
   117  func generateClientObjects() client.WithWatch {
   118  	return fake.NewClientBuilder().WithRuntimeObjects(
   119  		&networkv1.Ingress{
   120  			ObjectMeta: metav1.ObjectMeta{
   121  				Namespace: rancherNamespace,
   122  				Name:      rancherIngressName,
   123  			},
   124  			Spec: networkv1.IngressSpec{
   125  				Rules: []networkv1.IngressRule{
   126  					{
   127  						Host: "test-rancher.com",
   128  					},
   129  				},
   130  			},
   131  		},
   132  		&corev1.Secret{
   133  			ObjectMeta: metav1.ObjectMeta{
   134  				Namespace: rancherNamespace,
   135  				Name:      rancherTLSSecret,
   136  			},
   137  			Data: map[string][]byte{
   138  				"ca.crt": []byte(""),
   139  			},
   140  		},
   141  		&corev1.Secret{
   142  			ObjectMeta: metav1.ObjectMeta{
   143  				Namespace: constants.VerrazzanoMultiClusterNamespace,
   144  				Name:      pkgconst.VerrazzanoClusterRancherName,
   145  			},
   146  			Data: map[string][]byte{
   147  				"password": []byte(""),
   148  			},
   149  		},
   150  		&corev1.Secret{
   151  			ObjectMeta: metav1.ObjectMeta{
   152  				Namespace: rancherNamespace,
   153  				Name:      GetAgentSecretName("cluster"),
   154  			},
   155  		},
   156  		&corev1.Secret{
   157  			ObjectMeta: metav1.ObjectMeta{
   158  				Namespace: rancherNamespace,
   159  				Name:      GetRegistrationSecretName("cluster"),
   160  			},
   161  		},
   162  		&networkv1.Ingress{
   163  			ObjectMeta: metav1.ObjectMeta{
   164  				Name:      rancherNamespace,
   165  				Namespace: rancherIngressName,
   166  			},
   167  			Spec: networkv1.IngressSpec{Rules: []networkv1.IngressRule{{Host: "rancher.unit-test.com"}}},
   168  		},
   169  		&corev1.Secret{
   170  			ObjectMeta: metav1.ObjectMeta{
   171  				Namespace: rancherNamespace,
   172  				Name:      rancherAdminSecret,
   173  			},
   174  			Data: map[string][]byte{
   175  				"password": []byte("super-secret"),
   176  			},
   177  		},
   178  	).Build()
   179  }
   180  
   181  func addInactiveClusterMock(httpMock *mocks.MockRequestSender, clusterID string) *mocks.MockRequestSender {
   182  	addTokenMock(httpMock)
   183  
   184  	addVerrazzanoSystemNamespaceMock(httpMock, clusterID, true)
   185  	httpMock.EXPECT().
   186  		Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(clustersPath+"/"+clusterID)).
   187  		DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) {
   188  			var resp *http.Response
   189  			r := io.NopCloser(bytes.NewReader([]byte(`{"state":"inactive", "agentImage":"test"}`)))
   190  			resp = &http.Response{
   191  				StatusCode: http.StatusOK,
   192  				Body:       r,
   193  			}
   194  			return resp, nil
   195  		})
   196  	return httpMock
   197  }
   198  
   199  func addNoVerrazzanoSystemNSMock(httpMock *mocks.MockRequestSender, clusterID string) *mocks.MockRequestSender {
   200  	addTokenMock(httpMock)
   201  	addVerrazzanoSystemNamespaceMock(httpMock, clusterID, false)
   202  	return httpMock
   203  }
   204  
   205  func addActiveClusterMock(httpMock *mocks.MockRequestSender, a *asserts.Assertions, vmc *v1alpha1.VerrazzanoManagedCluster, r *VerrazzanoManagedClusterReconciler, clusterID string) *mocks.MockRequestSender {
   206  	httpMock = addTokenMock(httpMock)
   207  	addVerrazzanoSystemNamespaceMock(httpMock, clusterID, true)
   208  	agentSecret, err := r.getSecret(vmc.Namespace, GetAgentSecretName(vmc.Name), true)
   209  	a.NoError(err)
   210  	agentSecret.Namespace = constants.VerrazzanoSystemNamespace
   211  	agentSecret.Name = constants.MCAgentSecret
   212  	httpMock = addNotFoundMock(httpMock, &agentSecret, clusterID)
   213  
   214  	regSecret, err := r.getSecret(vmc.Namespace, GetRegistrationSecretName(vmc.Name), true)
   215  	a.NoError(err)
   216  	regSecret.Namespace = constants.VerrazzanoSystemNamespace
   217  	regSecret.Name = constants.MCRegistrationSecret
   218  	httpMock = addNotFoundMock(httpMock, &regSecret, clusterID)
   219  
   220  	expectActiveCluster(httpMock)
   221  	return httpMock
   222  }
   223  
   224  func expectActiveCluster(httpMock *mocks.MockRequestSender) {
   225  	httpMock.EXPECT().
   226  		Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(clustersPath+"/"+clusterID)).
   227  		DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) {
   228  			var resp *http.Response
   229  			r := io.NopCloser(bytes.NewReader([]byte(`{"state":"active", "agentImage":"test"}`)))
   230  			resp = &http.Response{
   231  				StatusCode: http.StatusOK,
   232  				Body:       r,
   233  			}
   234  			return resp, nil
   235  		})
   236  }
   237  
   238  func addTokenMock(httpMock *mocks.MockRequestSender) *mocks.MockRequestSender {
   239  	httpMock.EXPECT().
   240  		Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(loginURIPath)).
   241  		DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) {
   242  			r := io.NopCloser(bytes.NewReader([]byte(`{"token":"unit-test-token"}`)))
   243  			resp := &http.Response{
   244  				StatusCode: http.StatusCreated,
   245  				Body:       r,
   246  				Request:    &http.Request{Method: http.MethodPost},
   247  			}
   248  			return resp, nil
   249  		}).AnyTimes()
   250  	return httpMock
   251  }
   252  
   253  func addVerrazzanoSystemNamespaceMock(httpMock *mocks.MockRequestSender, clusterID string, exists bool) *mocks.MockRequestSender {
   254  	status := http.StatusOK
   255  	if !exists {
   256  		status = http.StatusNotFound
   257  	}
   258  	httpMock.EXPECT().
   259  		Do(gomock.Not(gomock.Nil()), mockmatchers.MatchesURI(k8sClustersPath+clusterID+"/api/v1/namespaces/verrazzano-system")).
   260  		DoAndReturn(func(httpClient *http.Client, req *http.Request) (*http.Response, error) {
   261  			var resp *http.Response
   262  			r := io.NopCloser(bytes.NewReader([]byte(`{"kind":"table", "apiVersion":"meta.k8s.io/v1"}`)))
   263  			resp = &http.Response{
   264  				StatusCode: status,
   265  				Body:       r,
   266  			}
   267  			return resp, nil
   268  		})
   269  
   270  	return httpMock
   271  }