diff --git a/.gitignore b/.gitignore index e305aaa3..e06ff1df 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ test/e2e/generated/bindata.go .vscode .DS_Store + +lua_configuration/networking.istio.io/**/testdata/*.lua diff --git a/lua_configuration/convert_test_case_to_lua_object.go b/lua_configuration/convert_test_case_to_lua_object.go index d97e959a..325fc687 100644 --- a/lua_configuration/convert_test_case_to_lua_object.go +++ b/lua_configuration/convert_test_case_to_lua_object.go @@ -100,11 +100,12 @@ func objectToTable(path string) error { Annotations: testCase.Original.GetAnnotations(), Spec: testCase.Original.Object["spec"], }, - Matches: step.TrafficRoutingStrategy.Matches, - CanaryWeight: *weight, - StableWeight: 100 - *weight, - CanaryService: canaryService, - StableService: stableService, + Matches: step.TrafficRoutingStrategy.Matches, + CanaryWeight: *weight, + StableWeight: 100 - *weight, + CanaryService: canaryService, + StableService: stableService, + RequestHeaderModifier: step.TrafficRoutingStrategy.RequestHeaderModifier, } uList[fmt.Sprintf("step_%d", i)] = data } @@ -128,11 +129,12 @@ func objectToTable(path string) error { Annotations: testCase.Original.GetAnnotations(), Spec: testCase.Original.Object["spec"], }, - Matches: matches, - CanaryWeight: *weight, - StableWeight: 100 - *weight, - CanaryService: canaryService, - StableService: stableService, + Matches: matches, + CanaryWeight: *weight, + StableWeight: 100 - *weight, + CanaryService: canaryService, + StableService: stableService, + RequestHeaderModifier: trafficRouting.Spec.Strategy.RequestHeaderModifier, } uList["steps_0"] = data } else { diff --git a/lua_configuration/networking.istio.io/VirtualService/testdata/rollout_with_three_steps.yaml b/lua_configuration/networking.istio.io/VirtualService/testdata/rollout_with_three_steps.yaml index 7550ebc1..e2e213b9 100644 --- a/lua_configuration/networking.istio.io/VirtualService/testdata/rollout_with_three_steps.yaml +++ b/lua_configuration/networking.istio.io/VirtualService/testdata/rollout_with_three_steps.yaml @@ -19,6 +19,10 @@ rollout: - type: RegularExpression name: name value: ".*demo" + requestHeaderModifier: + set: + - name: "header-foo" + value: "bar" - matches: - headers: - type: Exact @@ -66,6 +70,10 @@ expected: exact: pc name: regex: .*demo + headers: + request: + set: + header-foo: bar route: - destination: host: svc-demo-canary diff --git a/lua_configuration/networking.istio.io/VirtualService/testdata/traffic_routing_with_a_match.yaml b/lua_configuration/networking.istio.io/VirtualService/testdata/traffic_routing_with_a_match.yaml index cd5b99cd..514494d7 100644 --- a/lua_configuration/networking.istio.io/VirtualService/testdata/traffic_routing_with_a_match.yaml +++ b/lua_configuration/networking.istio.io/VirtualService/testdata/traffic_routing_with_a_match.yaml @@ -13,6 +13,10 @@ trafficRouting: - type: RegularExpression name: name value: ".*demo" + requestHeaderModifier: + set: + - name: "header-foo" + value: "bar" objectRef: - service: svc-demo customNetworkRefs: @@ -51,6 +55,10 @@ expected: exact: pc name: regex: .*demo + headers: + request: + set: + header-foo: bar route: - destination: host: svc-demo diff --git a/lua_configuration/networking.istio.io/VirtualService/testdata/traffic_routing_with_matches.yaml b/lua_configuration/networking.istio.io/VirtualService/testdata/traffic_routing_with_matches.yaml index 26c7d7db..9c6dc55d 100644 --- a/lua_configuration/networking.istio.io/VirtualService/testdata/traffic_routing_with_matches.yaml +++ b/lua_configuration/networking.istio.io/VirtualService/testdata/traffic_routing_with_matches.yaml @@ -14,6 +14,10 @@ trafficRouting: - type: RegularExpression name: name value: ".*demo" + requestHeaderModifier: + set: + - name: "header-foo" + value: "bar" objectRef: - service: svc-demo customNetworkRefs: @@ -50,6 +54,10 @@ expected: - headers: name: regex: .*demo + headers: + request: + set: + header-foo: bar route: - destination: host: svc-demo @@ -58,6 +66,10 @@ expected: - headers: user-agent: exact: pc + headers: + request: + set: + header-foo: bar route: - destination: host: svc-demo diff --git a/lua_configuration/networking.istio.io/VirtualService/trafficRouting.lua b/lua_configuration/networking.istio.io/VirtualService/trafficRouting.lua index 956d67f8..57fed53e 100644 --- a/lua_configuration/networking.istio.io/VirtualService/trafficRouting.lua +++ b/lua_configuration/networking.istio.io/VirtualService/trafficRouting.lua @@ -43,7 +43,7 @@ function CalculateWeight(route, stableWeight, n) end -- generate routes with matches, insert a rule before other rules, only support http headers, cookies etc. -function GenerateRoutesWithMatches(spec, matches, stableService, canaryService) +function GenerateRoutesWithMatches(spec, matches, stableService, canaryService, requestHeaderModifier) for _, match in ipairs(matches) do local route = {} route["match"] = {} @@ -81,6 +81,23 @@ function GenerateRoutesWithMatches(spec, matches, stableService, canaryService) end end table.insert(route["match"], vsMatch) + if requestHeaderModifier then + route["headers"] = {} + route["headers"]["request"] = {} + for action, headers in pairs(requestHeaderModifier) do + if action == "set" or action == "add" then + route["headers"]["request"][action] = {} + for _, header in ipairs(headers) do + route["headers"]["request"][action][header["name"]] = header["value"] + end + elseif action == "remove" then + route["headers"]["request"]["remove"] = {} + for _, rHeader in ipairs(headers) do + table.insert(route["headers"]["request"]["remove"], rHeader) + end + end + end + end route.route = { { destination = {} @@ -130,7 +147,7 @@ end if (obj.matches and next(obj.matches) ~= nil) then - GenerateRoutesWithMatches(spec, obj.matches, obj.stableService, obj.canaryService) + GenerateRoutesWithMatches(spec, obj.matches, obj.stableService, obj.canaryService, obj.requestHeaderModifier) else GenerateRoutes(spec, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight, "http") GenerateRoutes(spec, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight, "tcp") diff --git a/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider.go b/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider.go index 5778e4a3..c2828769 100644 --- a/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider.go +++ b/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider.go @@ -39,6 +39,7 @@ import ( "k8s.io/klog/v2" utilpointer "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" + gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" ) const ( @@ -47,12 +48,13 @@ const ( ) type LuaData struct { - Data Data - CanaryWeight int32 - StableWeight int32 - Matches []v1beta1.HttpRouteMatch - CanaryService string - StableService string + Data Data + CanaryWeight int32 + StableWeight int32 + Matches []v1beta1.HttpRouteMatch + CanaryService string + StableService string + RequestHeaderModifier *gatewayv1beta1.HTTPRequestHeaderFilter } type Data struct { Spec interface{} `json:"spec,omitempty"` @@ -268,13 +270,15 @@ func (r *customController) executeLuaForCanary(spec Data, strategy *v1beta1.Traf // so we need to pass weight=-1 to indicate the case where weight is nil. weight = utilpointer.Int32(-1) } + data := &LuaData{ - Data: spec, - CanaryWeight: *weight, - StableWeight: 100 - *weight, - Matches: matches, - CanaryService: r.conf.CanaryService, - StableService: r.conf.StableService, + Data: spec, + CanaryWeight: *weight, + StableWeight: 100 - *weight, + Matches: matches, + CanaryService: r.conf.CanaryService, + StableService: r.conf.StableService, + RequestHeaderModifier: strategy.RequestHeaderModifier, } unObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(data) diff --git a/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider_test.go b/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider_test.go index f27d9400..82fc0f1a 100644 --- a/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider_test.go +++ b/pkg/trafficrouting/network/customNetworkProvider/custom_network_provider_test.go @@ -414,6 +414,78 @@ func TestEnsureRoutes(t *testing.T) { return done, hasError }, }, + { + name: "Do header-based traffic routing and set header for VirtualService", + getRoutes: func() *v1beta1.TrafficRoutingStrategy { + headerTypeExact := gatewayv1beta1.HeaderMatchExact + return &v1beta1.TrafficRoutingStrategy{ + Matches: []v1beta1.HttpRouteMatch{ + { + Headers: []gatewayv1beta1.HTTPHeaderMatch{ + { + Type: &headerTypeExact, + Name: "user_id", + Value: "123456", + }, + }, + }, + }, + RequestHeaderModifier: &gatewayv1beta1.HTTPRequestHeaderFilter{ + Set: []gatewayv1beta1.HTTPHeader{ + { + Name: "x-env-flag", + Value: "canary", + }, + }, + }, + } + }, + getUnstructureds: func() []*unstructured.Unstructured { + objects := make([]*unstructured.Unstructured, 0) + u := &unstructured.Unstructured{} + _ = u.UnmarshalJSON([]byte(virtualServiceDemo)) + u.SetAPIVersion("networking.istio.io/v1alpha3") + objects = append(objects, u) + + return objects + }, + getConfig: func() Config { + return Config{ + Key: "rollout-demo", + StableService: "echoserver", + CanaryService: "echoserver-canary", + TrafficConf: []v1beta1.ObjectRef{ + { + APIVersion: "networking.istio.io/v1alpha3", + Kind: "VirtualService", + Name: "echoserver", + }, + }, + } + }, + expectUnstructureds: func() []*unstructured.Unstructured { + objects := make([]*unstructured.Unstructured, 0) + u := &unstructured.Unstructured{} + _ = u.UnmarshalJSON([]byte(virtualServiceDemo)) + annotations := map[string]string{ + OriginalSpecAnnotation: `{"spec":{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver"}}]}]},"annotations":{"virtual":"test"}}`, + "virtual": "test", + } + u.SetAnnotations(annotations) + specStr := `{"hosts":["echoserver.example.com"],"http":[{"headers":{"request":{"set":{"x-env-flag":"canary"}}},"match":[{"headers":{"user_id":{"exact":"123456"}}}],"route":[{"destination":{"host":"echoserver-canary"}}]},{"route":[{"destination":{"host":"echoserver"}}]}]}` + var spec interface{} + _ = json.Unmarshal([]byte(specStr), &spec) + u.Object["spec"] = spec + objects = append(objects, u) + + return objects + }, + expectState: func() (bool, bool) { + done := false + hasError := false + return done, hasError + }, + }, { name: "test2, do traffic routing but failed to execute lua", getRoutes: func() *v1beta1.TrafficRoutingStrategy { @@ -638,11 +710,12 @@ func TestLuaScript(t *testing.T) { Annotations: testCase.Original.GetAnnotations(), Spec: testCase.Original.Object["spec"], }, - Matches: step.TrafficRoutingStrategy.Matches, - CanaryWeight: *weight, - StableWeight: 100 - *weight, - CanaryService: canaryService, - StableService: stableService, + Matches: step.TrafficRoutingStrategy.Matches, + CanaryWeight: *weight, + StableWeight: 100 - *weight, + CanaryService: canaryService, + StableService: stableService, + RequestHeaderModifier: step.TrafficRoutingStrategy.RequestHeaderModifier, } nSpec, err := executeLua(data, script) if err != nil { @@ -678,11 +751,12 @@ func TestLuaScript(t *testing.T) { Annotations: testCase.Original.GetAnnotations(), Spec: testCase.Original.Object["spec"], }, - Matches: matches, - CanaryWeight: *weight, - StableWeight: 100 - *weight, - CanaryService: canaryService, - StableService: stableService, + Matches: matches, + CanaryWeight: *weight, + StableWeight: 100 - *weight, + CanaryService: canaryService, + StableService: stableService, + RequestHeaderModifier: trafficRouting.Spec.Strategy.RequestHeaderModifier, } nSpec, err := executeLua(data, script) if err != nil { diff --git a/pkg/trafficrouting/network/customNetworkProvider/lua_configuration/networking.istio.io/VirtualService/trafficRouting.lua b/pkg/trafficrouting/network/customNetworkProvider/lua_configuration/networking.istio.io/VirtualService/trafficRouting.lua index 6db36933..b4d9a3dd 100644 --- a/pkg/trafficrouting/network/customNetworkProvider/lua_configuration/networking.istio.io/VirtualService/trafficRouting.lua +++ b/pkg/trafficrouting/network/customNetworkProvider/lua_configuration/networking.istio.io/VirtualService/trafficRouting.lua @@ -94,7 +94,7 @@ function CalculateWeight(route, stableWeight, n) end -- generate routes with matches, insert a rule before other rules -function GenerateMatchedRoutes(spec, matches, stableService, canaryService, stableWeight, canaryWeight, protocol) +function GenerateMatchedRoutes(spec, matches, stableService, canaryService, stableWeight, canaryWeight, requestHeaderModifier, protocol) local hasRule, stableServiceSubsets = FindStableServiceSubsets(spec, stableService, protocol) if (not hasRule) then return @@ -136,6 +136,23 @@ function GenerateMatchedRoutes(spec, matches, stableService, canaryService, stab end end table.insert(route["match"], vsMatch) + if requestHeaderModifier then + route["headers"] = {} + route["headers"]["request"] = {} + for action, headers in pairs(requestHeaderModifier) do + if action == "set" or action == "add" then + route["headers"]["request"][action] = {} + for _, header in ipairs(headers) do + route["headers"]["request"][action][header["name"]] = header["value"] + end + elseif action == "remove" then + route["headers"]["request"]["remove"] = {} + for _, rHeader in ipairs(headers) do + table.insert(route["headers"]["request"]["remove"], rHeader) + end + end + end + end route.route = { { destination = {} @@ -187,7 +204,7 @@ function GenerateMatchedRoutes(spec, matches, stableService, canaryService, stab end -- generate routes without matches, change every rule -function GenerateRoutes(spec, stableService, canaryService, stableWeight, canaryWeight, protocol) +function GenerateRoutes(spec, stableService, canaryService, stableWeight, canaryWeight, requestHeaderModifier, protocol) local matchedRules = FindMatchedRules(spec, stableService, protocol) for _, rule in ipairs(matchedRules) do local canary @@ -218,12 +235,12 @@ function GenerateRoutes(spec, stableService, canaryService, stableWeight, canary end if (obj.matches) then - GenerateMatchedRoutes(spec, obj.matches, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight, "http") - GenerateMatchedRoutes(spec, obj.matches, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight, "tcp") - GenerateMatchedRoutes(spec, obj.matches, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight, "tls") + GenerateMatchedRoutes(spec, obj.matches, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight, obj.requestHeaderModifier, "http") + GenerateMatchedRoutes(spec, obj.matches, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight, obj.requestHeaderModifier,"tcp") + GenerateMatchedRoutes(spec, obj.matches, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight, obj.requestHeaderModifier,"tls") else - GenerateRoutes(spec, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight, "http") - GenerateRoutes(spec, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight, "tcp") - GenerateRoutes(spec, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight, "tls") + GenerateRoutes(spec, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight, obj.requestHeaderModifier, "http") + GenerateRoutes(spec, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight, obj.requestHeaderModifier, "tcp") + GenerateRoutes(spec, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight, obj.requestHeaderModifier, "tls") end return obj.data