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 }