github.com/oam-dev/kubevela@v1.9.11/pkg/workflow/providers/multicluster/deploy_test.go (about) 1 /* 2 Copyright 2022 The KubeVela Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package multicluster 18 19 import ( 20 "context" 21 "fmt" 22 "math/rand" 23 "sync" 24 "testing" 25 "time" 26 27 "github.com/stretchr/testify/require" 28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 29 "k8s.io/apimachinery/pkg/runtime" 30 31 workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1" 32 "github.com/kubevela/workflow/pkg/cue/model/value" 33 34 apicommon "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 35 36 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1" 37 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 38 "github.com/oam-dev/kubevela/pkg/oam" 39 ) 40 41 func TestOverrideConfiguration(t *testing.T) { 42 testCases := map[string]struct { 43 Policies []v1beta1.AppPolicy 44 Components []apicommon.ApplicationComponent 45 Outputs []apicommon.ApplicationComponent 46 Error string 47 }{ 48 "invalid-policies": { 49 Policies: []v1beta1.AppPolicy{{ 50 Name: "override-policy", 51 Type: "override", 52 Properties: &runtime.RawExtension{Raw: []byte(`bad value`)}, 53 }}, 54 Error: "failed to parse override policy", 55 }, 56 "empty-policy": { 57 Policies: []v1beta1.AppPolicy{{ 58 Name: "override-policy", 59 Type: "override", 60 Properties: nil, 61 }}, 62 Error: "empty properties", 63 }, 64 "normal": { 65 Policies: []v1beta1.AppPolicy{{ 66 Name: "override-policy", 67 Type: "override", 68 Properties: &runtime.RawExtension{Raw: []byte(`{"components":[{"name":"comp","properties":{"x":5}}]}`)}, 69 }}, 70 Components: []apicommon.ApplicationComponent{{ 71 Name: "comp", 72 Traits: []apicommon.ApplicationTrait{}, 73 Properties: &runtime.RawExtension{Raw: []byte(`{"x":1}`)}, 74 }}, 75 Outputs: []apicommon.ApplicationComponent{{ 76 Name: "comp", 77 Traits: []apicommon.ApplicationTrait{}, 78 Properties: &runtime.RawExtension{Raw: []byte(`{"x":5}`)}, 79 }}, 80 }, 81 } 82 for name, tt := range testCases { 83 t.Run(name, func(t *testing.T) { 84 r := require.New(t) 85 comps, err := overrideConfiguration(tt.Policies, tt.Components) 86 if tt.Error != "" { 87 r.NotNil(err) 88 r.Contains(err.Error(), tt.Error) 89 } else { 90 r.NoError(err) 91 r.Equal(tt.Outputs, comps) 92 } 93 }) 94 } 95 } 96 97 func TestApplyComponentsDepends(t *testing.T) { 98 r := require.New(t) 99 const n, m = 50, 5 100 var components []apicommon.ApplicationComponent 101 var placements []v1alpha1.PlacementDecision 102 for i := 0; i < n*3; i++ { 103 comp := apicommon.ApplicationComponent{Name: fmt.Sprintf("comp-%d", i)} 104 if i%3 != 0 { 105 comp.DependsOn = append(comp.DependsOn, fmt.Sprintf("comp-%d", i-1)) 106 } 107 if i%3 == 2 { 108 comp.DependsOn = append(comp.DependsOn, fmt.Sprintf("comp-%d", i-1)) 109 } 110 components = append(components, comp) 111 } 112 for i := 0; i < m; i++ { 113 placements = append(placements, v1alpha1.PlacementDecision{Cluster: fmt.Sprintf("cluster-%d", i)}) 114 } 115 116 applyMap := &sync.Map{} 117 apply := func(_ context.Context, comp apicommon.ApplicationComponent, patcher *value.Value, clusterName string, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, bool, error) { 118 time.Sleep(time.Duration(rand.Intn(200)+25) * time.Millisecond) 119 applyMap.Store(fmt.Sprintf("%s/%s", clusterName, comp.Name), true) 120 return nil, nil, true, nil 121 } 122 healthCheck := func(_ context.Context, comp apicommon.ApplicationComponent, patcher *value.Value, clusterName string, overrideNamespace string) (bool, *unstructured.Unstructured, []*unstructured.Unstructured, error) { 123 _, found := applyMap.Load(fmt.Sprintf("%s/%s", clusterName, comp.Name)) 124 return found, nil, nil, nil 125 } 126 parallelism := 10 127 128 countMap := func() int { 129 cnt := 0 130 applyMap.Range(func(key, value interface{}) bool { 131 cnt++ 132 return true 133 }) 134 return cnt 135 } 136 ctx := context.Background() 137 healthy, _, err := applyComponents(ctx, apply, healthCheck, components, placements, parallelism) 138 r.NoError(err) 139 r.False(healthy) 140 r.Equal(n*m, countMap()) 141 142 healthy, _, err = applyComponents(ctx, apply, healthCheck, components, placements, parallelism) 143 r.NoError(err) 144 r.False(healthy) 145 r.Equal(2*n*m, countMap()) 146 147 healthy, _, err = applyComponents(ctx, apply, healthCheck, components, placements, parallelism) 148 r.NoError(err) 149 r.True(healthy) 150 r.Equal(3*n*m, countMap()) 151 } 152 153 func TestApplyComponentsIO(t *testing.T) { 154 r := require.New(t) 155 156 var ( 157 parallelism = 10 158 applyMap = new(sync.Map) 159 ctx = context.Background() 160 ) 161 apply := func(_ context.Context, comp apicommon.ApplicationComponent, patcher *value.Value, clusterName string, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, bool, error) { 162 time.Sleep(time.Duration(rand.Intn(200)+25) * time.Millisecond) 163 applyMap.Store(fmt.Sprintf("%s/%s", clusterName, comp.Name), true) 164 return nil, nil, true, nil 165 } 166 healthCheck := func(_ context.Context, comp apicommon.ApplicationComponent, patcher *value.Value, clusterName string, overrideNamespace string) (bool, *unstructured.Unstructured, []*unstructured.Unstructured, error) { 167 _, found := applyMap.Load(fmt.Sprintf("%s/%s", clusterName, comp.Name)) 168 return found, &unstructured.Unstructured{Object: map[string]interface{}{ 169 "spec": map[string]interface{}{ 170 "path": fmt.Sprintf("%s/%s", clusterName, comp.Name), 171 }, 172 }}, []*unstructured.Unstructured{ 173 { 174 Object: map[string]interface{}{ 175 "metadata": map[string]interface{}{ 176 "labels": map[string]interface{}{ 177 oam.TraitResource: "obj", 178 }, 179 }, 180 "spec": map[string]interface{}{ 181 "path": fmt.Sprintf("%s/%s", clusterName, comp.Name), 182 }, 183 }, 184 }, 185 }, nil 186 } 187 188 resetStore := func() { 189 applyMap = &sync.Map{} 190 } 191 countMap := func() int { 192 cnt := 0 193 applyMap.Range(func(key, value interface{}) bool { 194 cnt++ 195 return true 196 }) 197 return cnt 198 } 199 200 t.Run("apply components with io successfully", func(t *testing.T) { 201 resetStore() 202 const n, m = 10, 5 203 var components []apicommon.ApplicationComponent 204 var placements []v1alpha1.PlacementDecision 205 for i := 0; i < n; i++ { 206 comp := apicommon.ApplicationComponent{ 207 Name: fmt.Sprintf("comp-%d", i), 208 Properties: &runtime.RawExtension{Raw: []byte(fmt.Sprintf(`{"placeholder":%d}`, i))}, 209 } 210 if i != 0 { 211 comp.Inputs = workflowv1alpha1.StepInputs{ 212 { 213 ParameterKey: "input_slot_1", 214 From: fmt.Sprintf("var-output-%d", i-1), 215 }, 216 { 217 ParameterKey: "input_slot_2", 218 From: fmt.Sprintf("var-outputs-%d", i-1), 219 }, 220 } 221 } 222 if i != n-1 { 223 comp.Outputs = workflowv1alpha1.StepOutputs{ 224 { 225 ValueFrom: "output.spec.path", 226 Name: fmt.Sprintf("var-output-%d", i), 227 }, 228 { 229 ValueFrom: "outputs.obj.spec.path", 230 Name: fmt.Sprintf("var-outputs-%d", i), 231 }, 232 } 233 } 234 components = append(components, comp) 235 } 236 for i := 0; i < m; i++ { 237 placements = append(placements, v1alpha1.PlacementDecision{Cluster: fmt.Sprintf("cluster-%d", i)}) 238 } 239 240 for i := 0; i < n; i++ { 241 healthy, _, err := applyComponents(ctx, apply, healthCheck, components, placements, parallelism) 242 r.NoError(err) 243 r.Equal((i+1)*m, countMap()) 244 if i == n-1 { 245 r.True(healthy) 246 } else { 247 r.False(healthy) 248 } 249 } 250 }) 251 252 t.Run("apply components with io failed", func(t *testing.T) { 253 resetStore() 254 components := []apicommon.ApplicationComponent{ 255 { 256 Name: "comp-0", 257 Outputs: workflowv1alpha1.StepOutputs{ 258 { 259 ValueFrom: "output.spec.error_path", 260 Name: "var1", 261 }, 262 }, 263 }, 264 { 265 Name: "comp-1", 266 Inputs: workflowv1alpha1.StepInputs{ 267 { 268 ParameterKey: "input_slot_1", 269 From: "var1", 270 }, 271 }, 272 }, 273 } 274 placements := []v1alpha1.PlacementDecision{ 275 {Cluster: "cluster-0"}, 276 } 277 healthy, _, err := applyComponents(ctx, apply, healthCheck, components, placements, parallelism) 278 r.NoError(err) 279 r.False(healthy) 280 healthy, _, err = applyComponents(ctx, apply, healthCheck, components, placements, parallelism) 281 r.ErrorContains(err, "failed to lookup value") 282 r.False(healthy) 283 }) 284 285 t.Run("apply components with io and replication", func(t *testing.T) { 286 // comp-0 ---> comp1-beijing --> comp2-beijing 287 // |-> comp1-shanghai --> comp2-shanghai 288 resetStore() 289 storeKey := func(clusterName string, comp apicommon.ApplicationComponent) string { 290 return fmt.Sprintf("%s/%s/%s", clusterName, comp.Name, comp.ReplicaKey) 291 } 292 type applyResult struct { 293 output *unstructured.Unstructured 294 outputs []*unstructured.Unstructured 295 } 296 apply := func(_ context.Context, comp apicommon.ApplicationComponent, patcher *value.Value, clusterName string, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, bool, error) { 297 time.Sleep(time.Duration(rand.Intn(200)+25) * time.Millisecond) 298 key := storeKey(clusterName, comp) 299 result := applyResult{ 300 output: &unstructured.Unstructured{Object: map[string]interface{}{ 301 "spec": map[string]interface{}{ 302 "path": key, 303 "anotherPath": key, 304 }, 305 }}, outputs: []*unstructured.Unstructured{ 306 { 307 Object: map[string]interface{}{ 308 "metadata": map[string]interface{}{ 309 "labels": map[string]interface{}{ 310 oam.TraitResource: "obj", 311 }, 312 }, 313 "spec": map[string]interface{}{ 314 "path": key, 315 }, 316 }, 317 }, 318 }, 319 } 320 applyMap.Store(storeKey(clusterName, comp), result) 321 return nil, nil, true, nil 322 } 323 healthCheck := func(_ context.Context, comp apicommon.ApplicationComponent, patcher *value.Value, clusterName string, overrideNamespace string) (bool, *unstructured.Unstructured, []*unstructured.Unstructured, error) { 324 key := storeKey(clusterName, comp) 325 r, found := applyMap.Load(key) 326 result, _ := r.(applyResult) 327 return found, result.output, result.outputs, nil 328 } 329 330 inputSlot := "input_slot" 331 components := []apicommon.ApplicationComponent{ 332 { 333 Name: "comp-0", 334 Outputs: workflowv1alpha1.StepOutputs{ 335 { 336 ValueFrom: "output.spec.path", 337 Name: "var1", 338 }, 339 }, 340 }, 341 { 342 Name: "comp-1", 343 Inputs: workflowv1alpha1.StepInputs{ 344 { 345 ParameterKey: inputSlot, 346 From: "var1", 347 }, 348 }, 349 Outputs: workflowv1alpha1.StepOutputs{ 350 { 351 ValueFrom: "output.spec.anotherPath", 352 Name: "var2", 353 }, 354 }, 355 ReplicaKey: "beijing", 356 }, 357 { 358 Name: "comp-1", 359 Inputs: workflowv1alpha1.StepInputs{ 360 { 361 ParameterKey: inputSlot, 362 From: "var1", 363 }, 364 }, 365 Outputs: workflowv1alpha1.StepOutputs{ 366 { 367 ValueFrom: "output.spec.anotherPath", 368 Name: "var2", 369 }, 370 }, 371 ReplicaKey: "shanghai", 372 }, 373 { 374 Name: "comp-2", 375 Inputs: workflowv1alpha1.StepInputs{ 376 { 377 ParameterKey: inputSlot, 378 From: "var2", 379 }, 380 }, 381 ReplicaKey: "beijing", 382 }, 383 { 384 Name: "comp-2", 385 Inputs: workflowv1alpha1.StepInputs{ 386 { 387 ParameterKey: inputSlot, 388 From: "var2", 389 }, 390 }, 391 ReplicaKey: "shanghai", 392 }, 393 } 394 placements := []v1alpha1.PlacementDecision{ 395 {Cluster: "cluster-0"}, 396 } 397 healthy, _, err := applyComponents(ctx, apply, healthCheck, components, placements, parallelism) 398 r.NoError(err) 399 r.False(healthy) 400 401 healthy, _, err = applyComponents(ctx, apply, healthCheck, components, placements, parallelism) 402 r.NoError(err) 403 r.False(healthy) 404 405 healthy, _, err = applyComponents(ctx, apply, healthCheck, components, placements, parallelism) 406 r.NoError(err) 407 r.True(healthy) 408 409 }) 410 }