-
Notifications
You must be signed in to change notification settings - Fork 71
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
Changes from 5 commits
6e57766
4bf65e3
509341f
520ae57
8f52a42
452eeb3
5968405
82dab92
1e53b77
adfe506
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
package main | ||
|
||
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) | ||
if _, err := os.Stat(filepath.Join(dir, "testdata")); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is testdata directory included in this PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. testdata and lua scripts are not included in this PR. This function aims to convert testdata into lua objects. When the user wants to debug their lua scripts, they could use this function to generate lua objects and copy the generated objects to their lua scripts for debugging. |
||
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 { | ||
weight := step.TrafficRoutingStrategy.Weight | ||
if step.TrafficRoutingStrategy.Weight == nil { | ||
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 | ||
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") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -97,6 +97,7 @@ func (r *TrafficRoutingReconciler) Reconcile(ctx context.Context, req ctrl.Reque | |
newStatus := tr.Status.DeepCopy() | ||
if newStatus.Phase == "" { | ||
newStatus.Phase = v1alpha1.TrafficRoutingPhaseInitial | ||
newStatus.Message = "TrafficRouting is Initializing" | ||
} | ||
if !tr.DeletionTimestamp.IsZero() { | ||
newStatus.Phase = v1alpha1.TrafficRoutingPhaseTerminating | ||
|
@@ -124,8 +125,8 @@ func (r *TrafficRoutingReconciler) Reconcile(ctx context.Context, req ctrl.Reque | |
case v1alpha1.TrafficRoutingPhaseFinalizing: | ||
done, err = r.trafficRoutingManager.FinalisingTrafficRouting(newTrafficRoutingContext(tr), false) | ||
if done { | ||
newStatus.Phase = v1alpha1.TrafficRoutingPhaseHealthy | ||
newStatus.Message = "TrafficRouting is Healthy" | ||
newStatus.Phase = v1alpha1.TrafficRoutingPhaseInitial | ||
newStatus.Message = "TrafficRouting is Initializing" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why changing routing to initializing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changing the status to Healthy works fine for gateway and ingress since they only check if the resource exists when initializing, however, for custom network providers, the original configuration is stored in annotation in initialization, and the annotation will be removed after finalising, without this annotation, customController can't do EnsureRoutes(). So it is necessary to first initialize after finalising, or next time customController will not work. |
||
} | ||
case v1alpha1.TrafficRoutingPhaseTerminating: | ||
done, err = r.trafficRoutingManager.FinalisingTrafficRouting(newTrafficRoutingContext(tr), false) | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.