github.com/verrazzano/verrazzano@v1.7.1/tools/psr/backend/workers/opensearch/restart/restart_test.go (about)

     1  // Copyright (c) 2022, 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 restart
     5  
     6  import (
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/verrazzano/verrazzano/pkg/constants"
    11  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    12  	"github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1alpha1"
    13  	"github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1beta1"
    14  	vpoFakeClient "github.com/verrazzano/verrazzano/platform-operator/clientset/versioned/fake"
    15  	"github.com/verrazzano/verrazzano/tools/psr/backend/config"
    16  	"github.com/verrazzano/verrazzano/tools/psr/backend/osenv"
    17  	"github.com/verrazzano/verrazzano/tools/psr/backend/pkg/k8sclient"
    18  	opensearchpsr "github.com/verrazzano/verrazzano/tools/psr/backend/pkg/opensearch"
    19  
    20  	"github.com/stretchr/testify/assert"
    21  	appsv1 "k8s.io/api/apps/v1"
    22  	corev1 "k8s.io/api/core/v1"
    23  	k8sapiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/runtime"
    26  	"k8s.io/apimachinery/pkg/types"
    27  	crtFakeClient "sigs.k8s.io/controller-runtime/pkg/client/fake"
    28  )
    29  
    30  type fakeEnv struct {
    31  	data map[string]string
    32  }
    33  
    34  type fakePsrClient struct {
    35  	psrClient *k8sclient.PsrClient
    36  }
    37  
    38  // TestGetters tests the worker getters
    39  // GIVEN a worker
    40  //
    41  //	WHEN the getter methods are calls
    42  //	THEN ensure that the correct results are returned
    43  func TestGetters(t *testing.T) {
    44  	origFunc := overridePsrClient()
    45  	defer func() {
    46  		funcNewPsrClient = origFunc
    47  	}()
    48  
    49  	envMap := map[string]string{
    50  		openSearchTier: opensearchpsr.MasterTier,
    51  	}
    52  	f := fakeEnv{data: envMap}
    53  	saveEnv := osenv.GetEnvFunc
    54  	osenv.GetEnvFunc = f.GetEnv
    55  	defer func() {
    56  		osenv.GetEnvFunc = saveEnv
    57  	}()
    58  
    59  	w, err := NewRestartWorker()
    60  	assert.NoError(t, err)
    61  
    62  	wd := w.GetWorkerDesc()
    63  	assert.Equal(t, config.WorkerTypeOpsRestart, wd.WorkerType)
    64  	assert.Equal(t, "Worker to restart pods in the specified OpenSearch tier", wd.Description)
    65  	assert.Equal(t, metricsPrefix, wd.MetricsPrefix)
    66  
    67  	logged := w.WantLoopInfoLogged()
    68  	assert.False(t, logged)
    69  }
    70  
    71  // TestGetEnvDescList tests the GetEnvDescList method
    72  // GIVEN a worker
    73  //
    74  //	WHEN the GetEnvDescList methods is called
    75  //	THEN ensure that the correct results are returned
    76  func TestGetEnvDescList(t *testing.T) {
    77  	origFunc := overridePsrClient()
    78  	defer func() {
    79  		funcNewPsrClient = origFunc
    80  	}()
    81  
    82  	envMap := map[string]string{
    83  		openSearchTier: opensearchpsr.MasterTier,
    84  	}
    85  	f := fakeEnv{data: envMap}
    86  	saveEnv := osenv.GetEnvFunc
    87  	osenv.GetEnvFunc = f.GetEnv
    88  	defer func() {
    89  		osenv.GetEnvFunc = saveEnv
    90  	}()
    91  
    92  	tests := []struct {
    93  		name     string
    94  		key      string
    95  		defval   string
    96  		required bool
    97  	}{
    98  		{name: "1",
    99  			key:      openSearchTier,
   100  			defval:   "",
   101  			required: true,
   102  		},
   103  	}
   104  
   105  	for _, test := range tests {
   106  		t.Run(test.name, func(t *testing.T) {
   107  			w, err := NewRestartWorker()
   108  			assert.NoError(t, err)
   109  			el := w.GetEnvDescList()
   110  			for _, e := range el {
   111  				if e.Key == test.key {
   112  					assert.Equal(t, test.defval, e.DefaultVal)
   113  					assert.Equal(t, test.required, e.Required)
   114  				}
   115  			}
   116  		})
   117  	}
   118  }
   119  
   120  // TestGetMetricDescList tests the GetEnvDescList method
   121  // GIVEN a worker
   122  //
   123  //	WHEN the GetEnvDescList methods is called
   124  //	THEN ensure that the correct results are returned
   125  func TestGetMetricDescList(t *testing.T) {
   126  	origFunc := overridePsrClient()
   127  	defer func() {
   128  		funcNewPsrClient = origFunc
   129  	}()
   130  
   131  	envMap := map[string]string{
   132  		openSearchTier: opensearchpsr.MasterTier,
   133  	}
   134  	f := fakeEnv{data: envMap}
   135  	saveEnv := osenv.GetEnvFunc
   136  	osenv.GetEnvFunc = f.GetEnv
   137  	defer func() {
   138  		osenv.GetEnvFunc = saveEnv
   139  	}()
   140  
   141  	tests := []struct {
   142  		name   string
   143  		fqName string
   144  		help   string
   145  		label  string
   146  	}{
   147  		{name: "1", fqName: metricsPrefix + "_pod_restart_count", help: "The total number of OpenSearch pod restarts", label: `opensearch_tier="master"`},
   148  		{name: "2", fqName: metricsPrefix + "_pod_restart_time_nanoseconds", help: "The number of nanoseconds elapsed to restart the OpenSearch pod", label: `opensearch_tier="master"`},
   149  	}
   150  	for _, test := range tests {
   151  		t.Run(test.name, func(t *testing.T) {
   152  			wi, err := NewRestartWorker()
   153  			w := wi.(worker)
   154  			assert.NoError(t, err)
   155  			dl := w.GetMetricDescList()
   156  			var found int
   157  			for _, d := range dl {
   158  				s := d.String()
   159  				if strings.Contains(s, test.fqName) && strings.Contains(s, test.help) {
   160  					found++
   161  				}
   162  			}
   163  			assert.Equal(t, 1, found)
   164  		})
   165  	}
   166  }
   167  
   168  // TestGetMetricList tests the GetMetricList method
   169  // GIVEN a worker
   170  //
   171  //	WHEN the GetMetricList methods is called
   172  //	THEN ensure that the correct results are returned
   173  func TestGetMetricList(t *testing.T) {
   174  	origFunc := overridePsrClient()
   175  	defer func() {
   176  		funcNewPsrClient = origFunc
   177  	}()
   178  
   179  	envMap := map[string]string{
   180  		openSearchTier: opensearchpsr.MasterTier,
   181  	}
   182  	f := fakeEnv{data: envMap}
   183  	saveEnv := osenv.GetEnvFunc
   184  	osenv.GetEnvFunc = f.GetEnv
   185  	defer func() {
   186  		osenv.GetEnvFunc = saveEnv
   187  	}()
   188  
   189  	tests := []struct {
   190  		name   string
   191  		fqName string
   192  		help   string
   193  	}{
   194  		{name: "1", fqName: metricsPrefix + "_pod_restart_count", help: "The total number of OpenSearch pod restarts"},
   195  		{name: "2", fqName: metricsPrefix + "_pod_restart_time_nanoseconds", help: "The number of nanoseconds elapsed to restart the OpenSearch pod"},
   196  	}
   197  	for _, test := range tests {
   198  		t.Run(test.name, func(t *testing.T) {
   199  			wi, err := NewRestartWorker()
   200  			w := wi.(worker)
   201  			assert.NoError(t, err)
   202  			ml := w.GetMetricList()
   203  			var found int
   204  			for _, m := range ml {
   205  				s := m.Desc().String()
   206  				if strings.Contains(s, test.fqName) && strings.Contains(s, test.help) {
   207  					found++
   208  				}
   209  			}
   210  			assert.Equal(t, 1, found)
   211  		})
   212  	}
   213  }
   214  
   215  // TestDoWork tests the DoWork method
   216  // GIVEN a worker
   217  //
   218  //	WHEN the DoWork methods is called
   219  //	THEN ensure that the correct results are returned
   220  func TestDoWork(t *testing.T) {
   221  	readyState := "ready"
   222  	notReadyState := "notready"
   223  	podExistsState := "podexists"
   224  	podUID := "poduid"
   225  
   226  	tests := []struct {
   227  		name  string
   228  		tier  string
   229  		state string
   230  	}{
   231  		{
   232  			name:  "master-ready",
   233  			tier:  opensearchpsr.MasterTier,
   234  			state: readyState,
   235  		},
   236  		{
   237  			name:  "data-ready",
   238  			tier:  opensearchpsr.DataTier,
   239  			state: readyState,
   240  		},
   241  		{
   242  			name:  "ingest-ready",
   243  			tier:  opensearchpsr.IngestTier,
   244  			state: readyState,
   245  		},
   246  		{
   247  			name:  "master-not-ready",
   248  			tier:  opensearchpsr.MasterTier,
   249  			state: notReadyState,
   250  		},
   251  		{
   252  			name:  "data-not-ready",
   253  			tier:  opensearchpsr.DataTier,
   254  			state: notReadyState,
   255  		},
   256  		{
   257  			name:  "ingest-not-ready",
   258  			tier:  opensearchpsr.IngestTier,
   259  			state: notReadyState,
   260  		},
   261  		{
   262  			name:  "master-pod-exists",
   263  			tier:  opensearchpsr.MasterTier,
   264  			state: podExistsState,
   265  		},
   266  		{
   267  			name:  "data-pod-exists",
   268  			tier:  opensearchpsr.DataTier,
   269  			state: podExistsState,
   270  		},
   271  		{
   272  			name:  "ingest-pod-exists",
   273  			tier:  opensearchpsr.IngestTier,
   274  			state: podExistsState,
   275  		},
   276  	}
   277  	for _, test := range tests {
   278  		t.Run(test.name, func(t *testing.T) {
   279  			envMap := map[string]string{
   280  				openSearchTier: test.tier,
   281  			}
   282  			f := fakeEnv{data: envMap}
   283  			saveEnv := osenv.GetEnvFunc
   284  			osenv.GetEnvFunc = f.GetEnv
   285  			defer func() {
   286  				osenv.GetEnvFunc = saveEnv
   287  			}()
   288  
   289  			// Setup fake VZ client
   290  			cr := &v1beta1.Verrazzano{
   291  				ObjectMeta: metav1.ObjectMeta{
   292  					Name: "testVZ",
   293  				},
   294  			}
   295  			vzclient := vpoFakeClient.NewSimpleClientset(cr)
   296  
   297  			// Setup fake K8s client
   298  			podLabels := getTierLabels(test.tier)
   299  			masterSTSLabel := map[string]string{"verrazzano-component": "opensearch"}
   300  			scheme := runtime.NewScheme()
   301  			_ = corev1.AddToScheme(scheme)
   302  			_ = k8sapiext.AddToScheme(scheme)
   303  			_ = v1alpha1.AddToScheme(scheme)
   304  			_ = appsv1.AddToScheme(scheme)
   305  			builder := crtFakeClient.NewClientBuilder().WithScheme(scheme)
   306  			if test.state == readyState {
   307  				builder = builder.WithObjects(initFakePodWithLabels(podLabels)).WithLists(initReadyDeployments(podLabels), initReadyStatefulSets(masterSTSLabel))
   308  			} else if test.state == notReadyState {
   309  				builder = builder.WithLists(initNotReadyDeployments(podLabels), initNotReadyStatefulSets(masterSTSLabel))
   310  			} else {
   311  				builder = builder.WithObjects(initFakePodWithLabels(podLabels)).WithLists(initReadyDeployments(podLabels), initReadyStatefulSets(masterSTSLabel))
   312  			}
   313  			crtClient := builder.Build()
   314  
   315  			// Load the PsrClient with both fake clients
   316  			psrClient := fakePsrClient{
   317  				psrClient: &k8sclient.PsrClient{
   318  					CrtlRuntime: crtClient,
   319  					VzInstall:   vzclient,
   320  				},
   321  			}
   322  			origFc := funcNewPsrClient
   323  			defer func() {
   324  				funcNewPsrClient = origFc
   325  			}()
   326  			funcNewPsrClient = psrClient.NewPsrClient
   327  
   328  			// Create worker and call dowork
   329  			wi, err := NewRestartWorker()
   330  			assert.NoError(t, err)
   331  			w := wi.(worker)
   332  			err = config.PsrEnv.LoadFromEnv(w.GetEnvDescList())
   333  			assert.NoError(t, err)
   334  			if test.state == podExistsState {
   335  				w.restartedPodUID = types.UID(podUID)
   336  			}
   337  			err = w.DoWork(config.CommonConfig{
   338  				WorkerType: "restart",
   339  			}, vzlog.DefaultLogger())
   340  			if test.state == readyState {
   341  				assert.NoError(t, err)
   342  				assert.True(t, w.restartStartTime > 0)
   343  				assert.Equal(t, w.restartedPodUID, types.UID(podUID))
   344  			} else if test.state == notReadyState {
   345  				assert.Error(t, err)
   346  				assert.False(t, w.restartStartTime > 0)
   347  				assert.NotEqual(t, w.restartedPodUID, types.UID(podUID))
   348  			} else {
   349  				assert.Error(t, err)
   350  				assert.False(t, w.restartStartTime > 0)
   351  				assert.Equal(t, w.restartedPodUID, types.UID(podUID))
   352  			}
   353  		})
   354  	}
   355  }
   356  
   357  // initFakePodWithLabels inits a fake Pod with specified image and labels
   358  func initFakePodWithLabels(labels map[string]string) *corev1.Pod {
   359  	return &corev1.Pod{
   360  		ObjectMeta: metav1.ObjectMeta{
   361  			Name:      "testPod",
   362  			Namespace: "verrazzano-system",
   363  			Labels:    labels,
   364  			UID:       "poduid",
   365  		},
   366  	}
   367  }
   368  
   369  func initReadyDeployments(labels map[string]string) *appsv1.DeploymentList {
   370  	return &appsv1.DeploymentList{
   371  		Items: []appsv1.Deployment{
   372  			{
   373  				ObjectMeta: metav1.ObjectMeta{
   374  					Name:      "vmi-system-es-ingest",
   375  					Namespace: constants.VerrazzanoSystemNamespace,
   376  					Labels:    labels,
   377  				},
   378  				Status: appsv1.DeploymentStatus{
   379  					Replicas:      3,
   380  					ReadyReplicas: 3,
   381  				},
   382  			},
   383  			{
   384  				ObjectMeta: metav1.ObjectMeta{
   385  					Name:      "vmi-system-es-data-0",
   386  					Namespace: constants.VerrazzanoSystemNamespace,
   387  					Labels:    labels,
   388  				},
   389  				Status: appsv1.DeploymentStatus{
   390  					Replicas:      1,
   391  					ReadyReplicas: 1,
   392  				},
   393  			},
   394  			{
   395  				ObjectMeta: metav1.ObjectMeta{
   396  					Name:      "vmi-system-es-data-1",
   397  					Namespace: constants.VerrazzanoSystemNamespace,
   398  					Labels:    labels,
   399  				},
   400  				Status: appsv1.DeploymentStatus{
   401  					Replicas:      1,
   402  					ReadyReplicas: 1,
   403  				},
   404  			},
   405  		},
   406  	}
   407  }
   408  
   409  func initNotReadyDeployments(labels map[string]string) *appsv1.DeploymentList {
   410  	return &appsv1.DeploymentList{
   411  		Items: []appsv1.Deployment{
   412  			{
   413  				ObjectMeta: metav1.ObjectMeta{
   414  					Name:      "vmi-system-es-ingest",
   415  					Namespace: constants.VerrazzanoSystemNamespace,
   416  					Labels:    labels,
   417  				},
   418  				Status: appsv1.DeploymentStatus{
   419  					Replicas:      2,
   420  					ReadyReplicas: 3,
   421  				},
   422  			},
   423  			{
   424  				ObjectMeta: metav1.ObjectMeta{
   425  					Name:      "vmi-system-es-data-0",
   426  					Namespace: constants.VerrazzanoSystemNamespace,
   427  					Labels:    labels,
   428  				},
   429  				Status: appsv1.DeploymentStatus{
   430  					Replicas:      1,
   431  					ReadyReplicas: 1,
   432  				},
   433  			},
   434  			{
   435  				ObjectMeta: metav1.ObjectMeta{
   436  					Name:      "vmi-system-es-data-1",
   437  					Namespace: constants.VerrazzanoSystemNamespace,
   438  					Labels:    labels,
   439  				},
   440  				Status: appsv1.DeploymentStatus{
   441  					Replicas:      0,
   442  					ReadyReplicas: 1,
   443  				},
   444  			},
   445  		},
   446  	}
   447  }
   448  
   449  func initReadyStatefulSets(labels map[string]string) *appsv1.StatefulSetList {
   450  	return &appsv1.StatefulSetList{
   451  		Items: []appsv1.StatefulSet{
   452  			{
   453  				ObjectMeta: metav1.ObjectMeta{
   454  					Name:      "vmi-system-es-master",
   455  					Namespace: constants.VerrazzanoSystemNamespace,
   456  					Labels:    labels,
   457  				},
   458  				Status: appsv1.StatefulSetStatus{
   459  					Replicas:      3,
   460  					ReadyReplicas: 3,
   461  				},
   462  			},
   463  		},
   464  	}
   465  }
   466  
   467  func initNotReadyStatefulSets(labels map[string]string) *appsv1.StatefulSetList {
   468  	return &appsv1.StatefulSetList{
   469  		Items: []appsv1.StatefulSet{
   470  			{
   471  				ObjectMeta: metav1.ObjectMeta{
   472  					Name:      "vmi-system-es-master",
   473  					Namespace: constants.VerrazzanoSystemNamespace,
   474  					Labels:    labels,
   475  				},
   476  				Status: appsv1.StatefulSetStatus{
   477  					Replicas:      2,
   478  					ReadyReplicas: 3,
   479  				},
   480  			},
   481  		},
   482  	}
   483  }
   484  
   485  func (f *fakeEnv) GetEnv(key string) string {
   486  	return f.data[key]
   487  }
   488  
   489  func (f *fakePsrClient) NewPsrClient() (k8sclient.PsrClient, error) {
   490  	return *f.psrClient, nil
   491  }
   492  
   493  func getTierLabels(tier string) map[string]string {
   494  	switch tier {
   495  	case opensearchpsr.MasterTier:
   496  		return map[string]string{"opensearch.verrazzano.io/role-master": "true"}
   497  	case opensearchpsr.DataTier:
   498  		return map[string]string{"opensearch.verrazzano.io/role-data": "true"}
   499  	case opensearchpsr.IngestTier:
   500  		return map[string]string{"opensearch.verrazzano.io/role-ingest": "true"}
   501  	default:
   502  		return nil
   503  	}
   504  }
   505  
   506  func overridePsrClient() func() (k8sclient.PsrClient, error) {
   507  	f := fakePsrClient{
   508  		psrClient: &k8sclient.PsrClient{},
   509  	}
   510  	origFc := funcNewPsrClient
   511  	funcNewPsrClient = f.NewPsrClient
   512  	return origFc
   513  }