diff --git a/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider.go b/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider.go index 7021134f..bf030d2d 100644 --- a/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider.go +++ b/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider.go @@ -305,31 +305,29 @@ func (r *customController) executeLuaForCanary(spec Data, strategy *v1beta1.Traf } func (r *customController) getLuaScript(ctx context.Context, ref v1beta1.ObjectRef) (string, error) { - // get local lua script - // luaScript.Provider: CRDGroupt/Kind + // First, try to get user-defined lua script from ConfigMap + // This allows users to override the built-in configuration group := strings.Split(ref.APIVersion, "/")[0] - key := fmt.Sprintf("lua_configuration/%s/trafficRouting.lua", fmt.Sprintf("%s/%s", group, ref.Kind)) - script := util.GetLuaConfigurationContent(key) - if script != "" { - return script, nil - } - - // if lua script is not found locally, then try ConfigMap nameSpace := util.GetRolloutNamespace() // kruise-rollout name := LuaConfigMap configMap := &corev1.ConfigMap{} err := r.Get(ctx, types.NamespacedName{Namespace: nameSpace, Name: name}, configMap) - if err != nil { - return "", fmt.Errorf("failed to get ConfigMap(%s/%s)", nameSpace, name) - } else { - // in format like "lua.traffic.routing.ingress.aliyun-alb" - key = fmt.Sprintf("%s.%s.%s", configuration.LuaTrafficRoutingCustomTypePrefix, ref.Kind, group) - if script, ok := configMap.Data[key]; ok { + if err == nil { + key := fmt.Sprintf("%s.%s.%s", configuration.LuaTrafficRoutingCustomTypePrefix, ref.Kind, group) + if script, ok := configMap.Data[key]; ok && script != "" { return script, nil - } else { - return "", fmt.Errorf("expected script not found neither locally nor in ConfigMap") } } + + // If user-defined script is not found, fall back to built-in lua script + // luaScript.Provider: CRDGroup/Kind + key := fmt.Sprintf("lua_configuration/%s/trafficRouting.lua", fmt.Sprintf("%s/%s", group, ref.Kind)) + script := util.GetLuaConfigurationContent(key) + if script != "" { + return script, nil + } + + return "", fmt.Errorf("lua script not found for %s/%s neither in ConfigMap nor built-in configuration", ref.Kind, group) } // compare and update obj, return whether the obj is updated diff --git a/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider_test.go b/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider_test.go index dacc036f..12fb6db1 100644 --- a/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider_test.go +++ b/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider_test.go @@ -221,6 +221,168 @@ func TestInitialize(t *testing.T) { } } +func TestGetLuaScriptPriority(t *testing.T) { + // Test that ConfigMap scripts take precedence over built-in scripts + cases := []struct { + name string + getConfig func() Config + getConfigMap func() *corev1.ConfigMap + expectedScript string + expectError bool + }{ + { + name: "ConfigMap script overrides built-in script for Istio VirtualService", + getConfig: func() Config { + return Config{ + StableService: "echoserver", + CanaryService: "echoserver-canary", + TrafficConf: []v1beta1.ObjectRef{ + { + APIVersion: "networking.istio.io/v1alpha3", + Kind: "VirtualService", + Name: "echoserver", + }, + }, + } + }, + getConfigMap: func() *corev1.ConfigMap { + return &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: LuaConfigMap, + Namespace: util.GetRolloutNamespace(), + }, + Data: map[string]string{ + fmt.Sprintf("%s.%s.%s", configuration.LuaTrafficRoutingCustomTypePrefix, "VirtualService", "networking.istio.io"): "-- Custom Lua script from ConfigMap\nreturn obj.data", + }, + } + }, + expectedScript: "-- Custom Lua script from ConfigMap\nreturn obj.data", + expectError: false, + }, + { + name: "ConfigMap script overrides built-in script for Istio DestinationRule", + getConfig: func() Config { + return Config{ + StableService: "echoserver", + CanaryService: "echoserver-canary", + TrafficConf: []v1beta1.ObjectRef{ + { + APIVersion: "networking.istio.io/v1alpha3", + Kind: "DestinationRule", + Name: "dr-demo", + }, + }, + } + }, + getConfigMap: func() *corev1.ConfigMap { + return &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: LuaConfigMap, + Namespace: util.GetRolloutNamespace(), + }, + Data: map[string]string{ + fmt.Sprintf("%s.%s.%s", configuration.LuaTrafficRoutingCustomTypePrefix, "DestinationRule", "networking.istio.io"): "-- Custom label: canary instead of gray\ncanary.labels['istio.service.tag'] = 'canary'\nreturn obj.data", + }, + } + }, + expectedScript: "-- Custom label: canary instead of gray\ncanary.labels['istio.service.tag'] = 'canary'\nreturn obj.data", + expectError: false, + }, + { + name: "Falls back to built-in script when ConfigMap doesn't have custom script", + getConfig: func() Config { + return Config{ + StableService: "echoserver", + CanaryService: "echoserver-canary", + TrafficConf: []v1beta1.ObjectRef{ + { + APIVersion: "networking.istio.io/v1alpha3", + Kind: "VirtualService", + Name: "echoserver", + }, + }, + } + }, + getConfigMap: func() *corev1.ConfigMap { + return &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: LuaConfigMap, + Namespace: util.GetRolloutNamespace(), + }, + Data: map[string]string{ + // Empty ConfigMap, should fall back to built-in + }, + } + }, + expectedScript: "", // Will check it's not empty and is the built-in script + expectError: false, + }, + { + name: "Returns error when neither ConfigMap nor built-in script exists", + getConfig: func() Config { + return Config{ + StableService: "echoserver", + CanaryService: "echoserver-canary", + TrafficConf: []v1beta1.ObjectRef{ + { + APIVersion: "non-existent.io/v1", + Kind: "NonExistentResource", + Name: "test", + }, + }, + } + }, + getConfigMap: func() *corev1.ConfigMap { + return &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: LuaConfigMap, + Namespace: util.GetRolloutNamespace(), + }, + Data: map[string]string{}, + } + }, + expectedScript: "", + expectError: true, + }, + } + + for _, cs := range cases { + t.Run(cs.name, func(t *testing.T) { + fakeCli := fake.NewClientBuilder().WithScheme(scheme).Build() + if err := fakeCli.Create(context.TODO(), cs.getConfigMap()); err != nil { + t.Fatalf("Failed to create ConfigMap: %s", err.Error()) + } + c, _ := NewCustomController(fakeCli, cs.getConfig()) + controller := c.(*customController) + + ref := cs.getConfig().TrafficConf[0] + script, err := controller.getLuaScript(context.TODO(), ref) + + if cs.expectError { + if err == nil { + t.Fatalf("Expected error but got none") + } + return + } + + if err != nil { + t.Fatalf("Unexpected error: %s", err.Error()) + } + + if cs.expectedScript != "" { + if script != cs.expectedScript { + t.Fatalf("Expected script:\n%s\n\nBut got:\n%s", cs.expectedScript, script) + } + } else { + // For the fallback case, just verify we got a non-empty built-in script + if script == "" { + t.Fatalf("Expected non-empty built-in script but got empty string") + } + } + }) + } +} + func checkEqual(cli client.Client, t *testing.T, expect *unstructured.Unstructured) { obj := &unstructured.Unstructured{} obj.SetAPIVersion(expect.GetAPIVersion())