github.com/verrazzano/verrazzano@v1.7.1/tools/psr/backend/workers/opensearch/scale/scale_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 scale
     5  
     6  import (
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    12  	"github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1alpha1"
    13  	vpoFakeClient "github.com/verrazzano/verrazzano/platform-operator/clientset/versioned/fake"
    14  	"github.com/verrazzano/verrazzano/tools/psr/backend/config"
    15  	"github.com/verrazzano/verrazzano/tools/psr/backend/osenv"
    16  	"github.com/verrazzano/verrazzano/tools/psr/backend/pkg/k8sclient"
    17  	opensearchpsr "github.com/verrazzano/verrazzano/tools/psr/backend/pkg/opensearch"
    18  	"github.com/verrazzano/verrazzano/tools/psr/backend/pkg/verrazzano"
    19  
    20  	"github.com/stretchr/testify/assert"
    21  	corev1 "k8s.io/api/core/v1"
    22  	k8sapiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    23  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  	"k8s.io/apimachinery/pkg/runtime"
    25  	crtFakeClient "sigs.k8s.io/controller-runtime/pkg/client/fake"
    26  )
    27  
    28  type fakeEnv struct {
    29  	data map[string]string
    30  }
    31  
    32  type fakePsrClient struct {
    33  	psrClient *k8sclient.PsrClient
    34  }
    35  
    36  // TestGetters tests the worker getters
    37  // GIVEN a worker
    38  //
    39  //	WHEN the getter methods are calls
    40  //	THEN ensure that the correct results are returned
    41  func TestGetters(t *testing.T) {
    42  	origFunc := overridePsrClient()
    43  	defer func() {
    44  		funcNewPsrClient = origFunc
    45  	}()
    46  
    47  	envMap := map[string]string{
    48  		openSearchTier: opensearchpsr.MasterTier,
    49  	}
    50  	f := fakeEnv{data: envMap}
    51  	saveEnv := osenv.GetEnvFunc
    52  	osenv.GetEnvFunc = f.GetEnv
    53  	defer func() {
    54  		osenv.GetEnvFunc = saveEnv
    55  	}()
    56  
    57  	w, err := NewScaleWorker()
    58  	assert.NoError(t, err)
    59  
    60  	wd := w.GetWorkerDesc()
    61  	assert.Equal(t, config.WorkerTypeOpsScale, wd.WorkerType)
    62  	assert.Equal(t, "The OpenSearch scale worker scales an OpenSearch tier in and out continuously", wd.Description)
    63  	assert.Equal(t, metricsPrefix, wd.MetricsPrefix)
    64  
    65  	logged := w.WantLoopInfoLogged()
    66  	assert.False(t, logged)
    67  }
    68  
    69  // TestGetEnvDescList tests the GetEnvDescList method
    70  // GIVEN a worker
    71  //
    72  //	WHEN the GetEnvDescList methods is called
    73  //	THEN ensure that the correct results are returned
    74  func TestGetEnvDescList(t *testing.T) {
    75  	origFunc := overridePsrClient()
    76  	defer func() {
    77  		funcNewPsrClient = origFunc
    78  	}()
    79  
    80  	envMap := map[string]string{
    81  		openSearchTier: opensearchpsr.MasterTier,
    82  	}
    83  	f := fakeEnv{data: envMap}
    84  	saveEnv := osenv.GetEnvFunc
    85  	osenv.GetEnvFunc = f.GetEnv
    86  	defer func() {
    87  		osenv.GetEnvFunc = saveEnv
    88  	}()
    89  
    90  	tests := []struct {
    91  		name     string
    92  		key      string
    93  		defval   string
    94  		required bool
    95  	}{
    96  		{name: "1",
    97  			key:      openSearchTier,
    98  			defval:   "",
    99  			required: true,
   100  		},
   101  		{name: "2",
   102  			key:      minReplicaCount,
   103  			defval:   "3",
   104  			required: false,
   105  		},
   106  		{name: "3",
   107  			key:      maxReplicaCount,
   108  			defval:   "5",
   109  			required: false,
   110  		},
   111  	}
   112  
   113  	for _, test := range tests {
   114  		t.Run(test.name, func(t *testing.T) {
   115  			w, err := NewScaleWorker()
   116  			assert.NoError(t, err)
   117  			el := w.GetEnvDescList()
   118  			for _, e := range el {
   119  				if e.Key == test.key {
   120  					assert.Equal(t, test.defval, e.DefaultVal)
   121  					assert.Equal(t, test.required, e.Required)
   122  				}
   123  			}
   124  		})
   125  	}
   126  }
   127  
   128  // TestGetMetricDescList tests the GetEnvDescList method
   129  // GIVEN a worker
   130  //
   131  //	WHEN the GetEnvDescList methods is called
   132  //	THEN ensure that the correct results are returned
   133  func TestGetMetricDescList(t *testing.T) {
   134  	origFunc := overridePsrClient()
   135  	defer func() {
   136  		funcNewPsrClient = origFunc
   137  	}()
   138  
   139  	envMap := map[string]string{
   140  		openSearchTier: opensearchpsr.MasterTier,
   141  	}
   142  	f := fakeEnv{data: envMap}
   143  	saveEnv := osenv.GetEnvFunc
   144  	osenv.GetEnvFunc = f.GetEnv
   145  	defer func() {
   146  		osenv.GetEnvFunc = saveEnv
   147  	}()
   148  
   149  	tests := []struct {
   150  		name   string
   151  		fqName string
   152  		help   string
   153  		label  string
   154  	}{
   155  		{name: "1", fqName: metricsPrefix + "_scale_out_count_total", help: "The total number of times OpenSearch scaled out", label: `opensearch_tier="master"`},
   156  		{name: "2", fqName: metricsPrefix + "_scale_in_count_total", help: "The total number of times OpenSearch scaled in", label: `opensearch_tier="master"`},
   157  		{name: "3", fqName: metricsPrefix + "_scale_out_seconds", help: "The number of seconds elapsed to scale out OpenSearch", label: `opensearch_tier="master"`},
   158  		{name: "4", fqName: metricsPrefix + "_scale_in_seconds", help: "The number of seconds elapsed to scale in OpenSearch", label: `opensearch_tier="master"`},
   159  	}
   160  	for _, test := range tests {
   161  		t.Run(test.name, func(t *testing.T) {
   162  			wi, err := NewScaleWorker()
   163  			w := wi.(worker)
   164  			assert.NoError(t, err)
   165  			dl := w.GetMetricDescList()
   166  			var found int
   167  			for _, d := range dl {
   168  				s := d.String()
   169  				if strings.Contains(s, test.fqName) && strings.Contains(s, test.help) {
   170  					found++
   171  				}
   172  			}
   173  			assert.Equal(t, 1, found)
   174  		})
   175  	}
   176  }
   177  
   178  // TestGetMetricList tests the GetMetricList method
   179  // GIVEN a worker
   180  //
   181  //	WHEN the GetMetricList methods is called
   182  //	THEN ensure that the correct results are returned
   183  func TestGetMetricList(t *testing.T) {
   184  	origFunc := overridePsrClient()
   185  	defer func() {
   186  		funcNewPsrClient = origFunc
   187  	}()
   188  
   189  	envMap := map[string]string{
   190  		openSearchTier: opensearchpsr.MasterTier,
   191  	}
   192  	f := fakeEnv{data: envMap}
   193  	saveEnv := osenv.GetEnvFunc
   194  	osenv.GetEnvFunc = f.GetEnv
   195  	defer func() {
   196  		osenv.GetEnvFunc = saveEnv
   197  	}()
   198  
   199  	tests := []struct {
   200  		name   string
   201  		fqName string
   202  		help   string
   203  	}{
   204  		{name: "1", fqName: metricsPrefix + "_scale_out_count_total", help: "The total number of times OpenSearch scaled out"},
   205  		{name: "2", fqName: metricsPrefix + "_scale_in_count_total", help: "The total number of times OpenSearch scaled in"},
   206  		{name: "3", fqName: metricsPrefix + "_scale_out_seconds", help: "The number of seconds elapsed to scale out OpenSearch"},
   207  		{name: "4", fqName: metricsPrefix + "_scale_in_seconds", help: "The number of seconds elapsed to scale in OpenSearch"},
   208  	}
   209  	for _, test := range tests {
   210  		t.Run(test.name, func(t *testing.T) {
   211  			wi, err := NewScaleWorker()
   212  			w := wi.(worker)
   213  			assert.NoError(t, err)
   214  			ml := w.GetMetricList()
   215  			var found int
   216  			for _, m := range ml {
   217  				s := m.Desc().String()
   218  				if strings.Contains(s, test.fqName) && strings.Contains(s, test.help) {
   219  					found++
   220  				}
   221  			}
   222  			assert.Equal(t, 1, found)
   223  		})
   224  	}
   225  }
   226  
   227  // TestDoWork tests the DoWork method
   228  // GIVEN a worker
   229  //
   230  //	WHEN the DoWork methods is called
   231  //	THEN ensure that the correct results are returned
   232  func TestDoWork(t *testing.T) {
   233  	tests := []struct {
   234  		name          string
   235  		tier          string
   236  		expectError   bool
   237  		minReplicas   string
   238  		maxReplicas   string
   239  		skipUpdate    bool
   240  		skipPodCreate bool
   241  		firstState    v1alpha1.VzStateType
   242  		secondState   v1alpha1.VzStateType
   243  		thirtState    v1alpha1.VzStateType
   244  	}{
   245  		{
   246  			name:        "master",
   247  			tier:        opensearchpsr.MasterTier,
   248  			expectError: false,
   249  			minReplicas: "3",
   250  			maxReplicas: "4",
   251  			firstState:  v1alpha1.VzStateReady,
   252  			secondState: v1alpha1.VzStateReconciling,
   253  		},
   254  		{
   255  			name:        "data",
   256  			tier:        opensearchpsr.DataTier,
   257  			expectError: false,
   258  			minReplicas: "3",
   259  			maxReplicas: "4",
   260  			firstState:  v1alpha1.VzStateReady,
   261  			secondState: v1alpha1.VzStateReconciling,
   262  		},
   263  		{
   264  			name:        "ingest",
   265  			tier:        opensearchpsr.IngestTier,
   266  			expectError: false,
   267  			minReplicas: "3",
   268  			maxReplicas: "4",
   269  			firstState:  v1alpha1.VzStateReady,
   270  			secondState: v1alpha1.VzStateReconciling,
   271  		},
   272  		{
   273  			name:        "replicaErr",
   274  			tier:        opensearchpsr.MasterTier,
   275  			skipUpdate:  true,
   276  			expectError: true,
   277  			minReplicas: "1",
   278  			maxReplicas: "4",
   279  			firstState:  v1alpha1.VzStateReady,
   280  		},
   281  		// Test missing pods
   282  		{
   283  			name:          "noPods",
   284  			tier:          opensearchpsr.MasterTier,
   285  			skipUpdate:    true,
   286  			skipPodCreate: true,
   287  			expectError:   true,
   288  			minReplicas:   "1",
   289  			maxReplicas:   "4",
   290  			firstState:    v1alpha1.VzStateReady,
   291  		},
   292  	}
   293  	for _, test := range tests {
   294  		t.Run(test.name, func(t *testing.T) {
   295  			envMap := map[string]string{
   296  				openSearchTier:  test.tier,
   297  				minReplicaCount: test.minReplicas,
   298  				maxReplicaCount: test.maxReplicas,
   299  			}
   300  			f := fakeEnv{data: envMap}
   301  			saveEnv := osenv.GetEnvFunc
   302  			osenv.GetEnvFunc = f.GetEnv
   303  			defer func() {
   304  				osenv.GetEnvFunc = saveEnv
   305  			}()
   306  
   307  			// Setup fake VZ client
   308  			cr := initFakeVzCr(test.firstState)
   309  			vzclient := vpoFakeClient.NewSimpleClientset(cr)
   310  
   311  			// Setup fake K8s client
   312  			podLabels := getTierLabels(test.tier)
   313  			scheme := runtime.NewScheme()
   314  			_ = corev1.AddToScheme(scheme)
   315  			_ = k8sapiext.AddToScheme(scheme)
   316  			_ = v1alpha1.AddToScheme(scheme)
   317  			builder := crtFakeClient.NewClientBuilder().WithScheme(scheme)
   318  			if !test.skipPodCreate {
   319  				builder = builder.WithObjects(initFakePodWithLabels(podLabels))
   320  			}
   321  			crtClient := builder.Build()
   322  
   323  			// Load the PsrClient with both fake clients
   324  			psrClient := fakePsrClient{
   325  				psrClient: &k8sclient.PsrClient{
   326  					CrtlRuntime: crtClient,
   327  					VzInstall:   vzclient,
   328  				},
   329  			}
   330  			origFc := funcNewPsrClient
   331  			defer func() {
   332  				funcNewPsrClient = origFc
   333  			}()
   334  			funcNewPsrClient = psrClient.NewPsrClient
   335  
   336  			// Create worker and call dowork
   337  			wi, err := NewScaleWorker()
   338  			assert.NoError(t, err)
   339  			w := wi.(worker)
   340  			err = config.PsrEnv.LoadFromEnv(w.GetEnvDescList())
   341  			assert.NoError(t, err)
   342  
   343  			// DoWork expects the Verrazzano CR state to change.
   344  			// Worker waits for CR to be ready, modifies it, then waits for it to be not ready (e.g. reconciling)
   345  			// Run a background thread to change the state after the work starts
   346  			if !test.skipUpdate {
   347  				go func() {
   348  					time.Sleep(1 * time.Second)
   349  					cr := initFakeVzCr(test.secondState)
   350  					verrazzano.UpdateVerrazzano(vzclient, cr)
   351  					if len(test.thirtState) > 0 {
   352  						time.Sleep(1 * time.Second)
   353  						cr := initFakeVzCr(test.thirtState)
   354  						verrazzano.UpdateVerrazzano(vzclient, cr)
   355  					}
   356  				}()
   357  			}
   358  
   359  			err = w.DoWork(config.CommonConfig{
   360  				WorkerType: "scale",
   361  			}, vzlog.DefaultLogger())
   362  			if test.expectError {
   363  				assert.Error(t, err)
   364  			} else {
   365  				assert.NoError(t, err)
   366  			}
   367  		})
   368  	}
   369  }
   370  
   371  // initFakePodWithLabels inits a fake Pod with specified image and labels
   372  func initFakePodWithLabels(labels map[string]string) *corev1.Pod {
   373  	return &corev1.Pod{
   374  		ObjectMeta: metav1.ObjectMeta{
   375  			Name:      "testPod",
   376  			Namespace: "verrazzano-system",
   377  			Labels:    labels,
   378  		},
   379  	}
   380  }
   381  
   382  // initFakeVzCr inits a fake Verrazzano CR
   383  func initFakeVzCr(state v1alpha1.VzStateType) *v1alpha1.Verrazzano {
   384  	return &v1alpha1.Verrazzano{
   385  		ObjectMeta: metav1.ObjectMeta{
   386  			Name:      "testPod",
   387  			Namespace: "verrazzano-system",
   388  		},
   389  		Status: v1alpha1.VerrazzanoStatus{
   390  			State: state,
   391  		},
   392  	}
   393  }
   394  
   395  func (f *fakeEnv) GetEnv(key string) string {
   396  	return f.data[key]
   397  }
   398  
   399  func (f *fakePsrClient) NewPsrClient() (k8sclient.PsrClient, error) {
   400  	return *f.psrClient, nil
   401  }
   402  
   403  func getTierLabels(tier string) map[string]string {
   404  	switch tier {
   405  	case opensearchpsr.MasterTier:
   406  		return map[string]string{"opensearch.verrazzano.io/role-master": "true"}
   407  	case opensearchpsr.DataTier:
   408  		return map[string]string{"opensearch.verrazzano.io/role-data": "true"}
   409  	case opensearchpsr.IngestTier:
   410  		return map[string]string{"opensearch.verrazzano.io/role-ingest": "true"}
   411  	default:
   412  		return nil
   413  	}
   414  }
   415  
   416  func overridePsrClient() func() (k8sclient.PsrClient, error) {
   417  	f := fakePsrClient{
   418  		psrClient: &k8sclient.PsrClient{},
   419  	}
   420  	origFc := funcNewPsrClient
   421  	funcNewPsrClient = f.NewPsrClient
   422  	return origFc
   423  }