240 lines
7.2 KiB
Go
240 lines
7.2 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/openkruise/rollouts/api/v1alpha1"
|
|
"github.com/openkruise/rollouts/api/v1beta1"
|
|
custom "github.com/openkruise/rollouts/pkg/trafficrouting/network/customNetworkProvider"
|
|
"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"
|
|
"k8s.io/apimachinery/pkg/util/intstr"
|
|
utilpointer "k8s.io/utils/pointer"
|
|
"sigs.k8s.io/yaml"
|
|
)
|
|
|
|
type TestCase struct {
|
|
Rollout *v1beta1.Rollout `json:"rollout,omitempty"`
|
|
TrafficRouting *v1alpha1.TrafficRouting `json:"trafficRouting,omitempty"`
|
|
Original *unstructured.Unstructured `json:"original,omitempty"`
|
|
Expected []*unstructured.Unstructured `json:"expected,omitempty"`
|
|
}
|
|
|
|
// this function aims to convert testdata to lua object for debugging
|
|
// run `go run lua.go`, then this program will get all testdata and convert them into lua objects
|
|
// copy the generated objects to lua scripts and then you can start debugging your lua scripts
|
|
func main() {
|
|
err := convertTestCaseToLuaObject()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
|
|
func convertTestCaseToLuaObject() 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)
|
|
if _, err := os.Stat(filepath.Join(dir, "testdata")); err != nil {
|
|
fmt.Printf("testdata not found in %s\n", dir)
|
|
return nil
|
|
}
|
|
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 {
|
|
var weight *int32
|
|
if step.TrafficRoutingStrategy.Traffic != nil {
|
|
is := intstr.FromString(*step.TrafficRoutingStrategy.Traffic)
|
|
weightInt, _ := intstr.GetScaledValueFromIntOrPercent(&is, 100, true)
|
|
weight = utilpointer.Int32(int32(weightInt))
|
|
} else {
|
|
weight = utilpointer.Int32(-1)
|
|
}
|
|
var canaryService string
|
|
stableService := rollout.Spec.Strategy.Canary.TrafficRoutings[0].Service
|
|
canaryService = fmt.Sprintf("%s-canary", 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
|
|
matches := make([]v1beta1.HttpRouteMatch, 0)
|
|
for _, match := range trafficRouting.Spec.Strategy.Matches {
|
|
obj := v1beta1.HttpRouteMatch{}
|
|
obj.Headers = match.Headers
|
|
matches = append(matches, obj)
|
|
}
|
|
data := &custom.LuaData{
|
|
Data: custom.Data{
|
|
Labels: testCase.Original.GetLabels(),
|
|
Annotations: testCase.Original.GetAnnotations(),
|
|
Spec: testCase.Original.Object["spec"],
|
|
},
|
|
Matches: 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()
|
|
header := "-- THIS IS GENERATED BY CONVERT_TEST_CASE_TO_LUA_OBJECT.GO FOR DEBUGGING --\n"
|
|
_, err = io.WriteString(fileStream, header+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")
|
|
}
|
|
}
|