Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for custom network provider (part A) #172

Merged
merged 10 commits into from
Sep 25, 2023
Merged
8 changes: 8 additions & 0 deletions api/v1alpha1/trafficrouting_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ type TrafficRoutingRef struct {
// Gateway holds Gateway specific configuration to route traffic
// Gateway configuration only supports >= v0.4.0 (v1alpha2).
Gateway *GatewayTrafficRouting `json:"gateway,omitempty"`
// CustomNetworkRefs hold a list of custom providers to route traffic
CustomNetworkRefs *[]CustomNetworkRef `json:"customNetworkRefs,omitempty"`
}

// IngressTrafficRouting configuration for ingress controller to control traffic routing
Expand Down Expand Up @@ -149,6 +151,12 @@ type TrafficRoutingList struct {
Items []TrafficRouting `json:"items"`
}

type CustomNetworkRef struct {
APIVersion string `json:"apiVersion,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the omitempty, because apiversion, kind, name always be not empty.

Kind string `json:"kind,omitempty"`
Name string `json:"name,omitempty"`
}

func init() {
SchemeBuilder.Register(&TrafficRouting{}, &TrafficRoutingList{})
}
24 changes: 24 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions config/crd/bases/rollouts.kruise.io_rollouts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,19 @@ spec:
for supported service meshes to enable more fine-grained
traffic routing
properties:
customNetworkRefs:
description: CustomNetworkRefs hold a list of custom
providers to route traffic
items:
properties:
apiVersion:
type: string
kind:
type: string
name:
type: string
type: object
type: array
gateway:
description: Gateway holds Gateway specific configuration
to route traffic Gateway configuration only supports
Expand Down
13 changes: 13 additions & 0 deletions config/crd/bases/rollouts.kruise.io_trafficroutings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ spec:
for supported service meshes to enable more fine-grained traffic
routing
properties:
customNetworkRefs:
description: CustomNetworkRefs hold a list of custom providers
to route traffic
items:
properties:
apiVersion:
type: string
kind:
type: string
name:
type: string
type: object
type: array
gateway:
description: Gateway holds Gateway specific configuration to
route traffic Gateway configuration only supports >= v0.4.0
Expand Down
225 changes: 225 additions & 0 deletions lua_configuration/lua.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
package main
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this source file a UT? can we run it when building the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is more like a plugin to facilitate the development of Lua scripts.


import (
"fmt"
"io"
"os"
"path/filepath"
"strings"

"github.com/openkruise/rollouts/api/v1alpha1"
"github.com/openkruise/rollouts/pkg/trafficrouting/network/custom"
"github.com/openkruise/rollouts/pkg/util/luamanager"
lua "github.com/yuin/gopher-lua"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/yaml"

utilpointer "k8s.io/utils/pointer"
)

type TestCase struct {
Rollout *v1alpha1.Rollout `json:"rollout,omitempty"`
TrafficRouting *v1alpha1.TrafficRouting `json:"trafficRouting,omitempty"`
Original *unstructured.Unstructured `json:"original,omitempty"`
Expected []*unstructured.Unstructured `json:"expected,omitempty"`
}

// convert testdata to lua object for debugging
func main() {
err := PathWalk()
if err != nil {
fmt.Println(err)
}
}

func PathWalk() error {
err := filepath.Walk("./", func(path string, f os.FileInfo, err error) error {
if !strings.Contains(path, "trafficRouting.lua") {
return nil
}
if err != nil {
return fmt.Errorf("failed to walk path: %s", err.Error())
}
dir := filepath.Dir(path)
err = filepath.Walk(filepath.Join(dir, "testdata"), func(path string, info os.FileInfo, err error) error {
if !info.IsDir() && filepath.Ext(path) == ".yaml" || filepath.Ext(path) == ".yml" {
fmt.Printf("--- walking path: %s ---\n", path)
err = ObjectToTable(path)
if err != nil {
return fmt.Errorf("failed to convert object to table: %s", err)
}
}
return nil
})
if err != nil {
return fmt.Errorf("failed to walk path: %s", err.Error())
}
return nil
})
if err != nil {
return fmt.Errorf("failed to walk path: %s", err)
}
return nil
}

// convert a testcase object to lua table for debug
func ObjectToTable(path string) error {
dir, file := filepath.Split(path)
testCase, err := getLuaTestCase(path)
if err != nil {
return fmt.Errorf("failed to get lua testcase: %s", err)
}
uList := make(map[string]interface{})
rollout := testCase.Rollout
trafficRouting := testCase.TrafficRouting
if rollout != nil {
steps := rollout.Spec.Strategy.Canary.Steps
for i, step := range steps {
weight := step.TrafficRoutingStrategy.Weight
if step.TrafficRoutingStrategy.Weight == nil {
weight = utilpointer.Int32(-1)
}
var canaryService string
stableService := rollout.Spec.Strategy.Canary.TrafficRoutings[0].Service
// if rollout.Spec.Strategy.Canary.TrafficRoutings[0].CreateCanaryService {
canaryService = fmt.Sprintf("%s-canary", stableService)
// } else {
// canaryService = stableService
// }
data := &custom.LuaData{
Data: custom.Data{
Labels: testCase.Original.GetLabels(),
Annotations: testCase.Original.GetAnnotations(),
Spec: testCase.Original.Object["spec"],
},
Matches: step.TrafficRoutingStrategy.Matches,
CanaryWeight: *weight,
StableWeight: 100 - *weight,
CanaryService: canaryService,
StableService: stableService,
}
uList[fmt.Sprintf("step_%d", i)] = data
}
} else if trafficRouting != nil {
weight := trafficRouting.Spec.Strategy.Weight
if weight == nil {
weight = utilpointer.Int32(-1)
}
var canaryService string
stableService := trafficRouting.Spec.ObjectRef[0].Service
canaryService = stableService
data := &custom.LuaData{
Data: custom.Data{
Labels: testCase.Original.GetLabels(),
Annotations: testCase.Original.GetAnnotations(),
Spec: testCase.Original.Object["spec"],
},
Matches: trafficRouting.Spec.Strategy.Matches,
CanaryWeight: *weight,
StableWeight: 100 - *weight,
CanaryService: canaryService,
StableService: stableService,
}
uList["steps_0"] = data
} else {
return fmt.Errorf("neither rollout nor trafficRouting defined in test case: %s", path)
}

objStr, err := executeLua(uList)
if err != nil {
return fmt.Errorf("failed to execute lua: %s", err.Error())
}
filePath := fmt.Sprintf("%s%s_obj.lua", dir, strings.Split(file, ".")[0])
fileStream, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666)
if err != nil {
return fmt.Errorf("failed to open file: %s", err)
}
defer fileStream.Close()
_, err = io.WriteString(fileStream, objStr)
if err != nil {
return fmt.Errorf("failed to WriteString %s", err)
}
return nil
}

func getLuaTestCase(path string) (*TestCase, error) {
yamlFile, err := os.ReadFile(path)
if err != nil {
return nil, err
}
luaTestCase := &TestCase{}
err = yaml.Unmarshal(yamlFile, luaTestCase)
if err != nil {
return nil, err
}
return luaTestCase, nil
}

func executeLua(steps map[string]interface{}) (string, error) {
luaManager := &luamanager.LuaManager{}
unObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&steps)
if err != nil {
return "", fmt.Errorf("failed to convert to unstructured: %s", err)
}
u := &unstructured.Unstructured{Object: unObj}
script := `
function serialize(obj, isKey)
local lua = ""
local t = type(obj)
if t == "number" then
lua = lua .. obj
elseif t == "boolean" then
lua = lua .. tostring(obj)
elseif t == "string" then
if isKey then
lua = lua .. string.format("%s", obj)
else
lua = lua .. string.format("%q", obj)
end
elseif t == "table" then
lua = lua .. "{"
for k, v in pairs(obj) do
if type(k) == "string" then
lua = lua .. serialize(k, true) .. "=" .. serialize(v, false) .. ","
else
lua = lua .. serialize(v, false) .. ","
end
end
local metatable = getmetatable(obj)
if metatable ~= nil and type(metatable.__index) == "table" then
for k, v in pairs(metatable.__index) do
if type(k) == "string" then
lua = lua .. serialize(k, true) .. "=" .. serialize(v, false) .. ","
else
lua = lua .. serialize(v, false) .. ","
end
end
end
lua = lua .. "}"
elseif t == "nil" then
return nil
else
error("can not serialize a " .. t .. " type.")
end
return lua
end

function table2string(tablevalue)
local stringtable = "steps=" .. serialize(tablevalue)
print(stringtable)
return stringtable
end
return table2string(obj)
`
l, err := luaManager.RunLuaScript(u, script)
if err != nil {
return "", fmt.Errorf("failed to run lua script: %s", err)
}
returnValue := l.Get(-1)
if returnValue.Type() == lua.LTString {
return returnValue.String(), nil
} else {
return "", fmt.Errorf("unexpected lua output type")
}
}
11 changes: 11 additions & 0 deletions pkg/trafficrouting/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/openkruise/rollouts/api/v1alpha1"
"github.com/openkruise/rollouts/pkg/trafficrouting/network"
"github.com/openkruise/rollouts/pkg/trafficrouting/network/custom"
"github.com/openkruise/rollouts/pkg/trafficrouting/network/gateway"
"github.com/openkruise/rollouts/pkg/trafficrouting/network/ingress"
"github.com/openkruise/rollouts/pkg/util"
Expand Down Expand Up @@ -263,6 +264,16 @@ func (m *Manager) FinalisingTrafficRouting(c *TrafficRoutingContext, onlyRestore

func newNetworkProvider(c client.Client, con *TrafficRoutingContext, sService, cService string) (network.NetworkProvider, error) {
trafficRouting := con.ObjectRef[0]
if trafficRouting.CustomNetworkRefs != nil {
return custom.NewCustomController(c, custom.Config{
Key: con.Key,
RolloutNs: con.Namespace,
CanaryService: cService,
StableService: sService,
TrafficConf: *trafficRouting.CustomNetworkRefs,
OwnerRef: con.OwnerRef,
})
}
if trafficRouting.Ingress != nil {
return ingress.NewIngressTrafficRouting(c, ingress.Config{
Key: con.Key,
Expand Down
Loading