finished lua script, controllerMap issue not done
Signed-off-by: Kuromesi <blackfacepan@163.com>
This commit is contained in:
parent
9fa42b98f2
commit
9a062e08cd
|
|
@ -1,45 +1,140 @@
|
|||
-- obj = {
|
||||
-- -- matches = {
|
||||
-- -- {
|
||||
-- -- headers = {
|
||||
-- -- {
|
||||
-- -- name = "xxx",
|
||||
-- -- value = "xxx",
|
||||
-- -- type = "RegularExpression"
|
||||
-- -- }
|
||||
-- -- }
|
||||
-- -- }
|
||||
-- -- },
|
||||
-- spec = {
|
||||
-- hosts = {
|
||||
-- "reviews",
|
||||
-- },
|
||||
-- http = {
|
||||
-- {
|
||||
-- route = {
|
||||
-- {
|
||||
-- destination = {
|
||||
-- host = "reviews",
|
||||
-- subset = "c1"
|
||||
-- }
|
||||
-- }
|
||||
-- matches = {
|
||||
-- {
|
||||
-- headers = {
|
||||
-- {
|
||||
-- name = "xxx",
|
||||
-- value = "xxx",
|
||||
-- type = "RegularExpression"
|
||||
-- }
|
||||
-- }
|
||||
-- }
|
||||
-- },
|
||||
-- data = {
|
||||
-- spec = {
|
||||
-- hosts = {
|
||||
-- "reviews",
|
||||
-- },
|
||||
-- http = {
|
||||
-- {
|
||||
-- route = {
|
||||
-- {
|
||||
-- destination = {
|
||||
-- host = "reviews",
|
||||
-- subset = "c1",
|
||||
-- port = {
|
||||
-- number = 80
|
||||
-- }
|
||||
-- }
|
||||
-- }
|
||||
-- }
|
||||
-- }
|
||||
-- }
|
||||
-- },
|
||||
-- },
|
||||
|
||||
-- stableService = "reviews",
|
||||
-- canaryService = "canary",
|
||||
-- stableWeight = 90,
|
||||
-- canaryWeight = 10
|
||||
-- }
|
||||
|
||||
spec = obj.data.spec
|
||||
|
||||
spec = obj.spec
|
||||
if (obj.matches) then
|
||||
for _, match in ipairs(obj.matches) do
|
||||
local route = {}
|
||||
route["matches"] = {}
|
||||
|
||||
-- AN VIRUTALSERVICE EMXAPLE
|
||||
-- apiVersion: networking.istio.io/v1alpha3
|
||||
-- kind: VirtualService
|
||||
-- metadata:
|
||||
-- name: productpage
|
||||
-- namespace: nsA
|
||||
-- spec:
|
||||
-- http:
|
||||
-- - match:
|
||||
-- - uri:
|
||||
-- prefix: "/productpage/v1/"
|
||||
-- route:
|
||||
-- - destination:
|
||||
-- host: productpage-v1.nsA.svc.cluster.local
|
||||
-- - route:
|
||||
-- - destination:
|
||||
-- host: productpage.nsA.svc.cluster.local
|
||||
|
||||
function DeepCopy(original)
|
||||
local copy
|
||||
if type(original) == 'table' then
|
||||
copy = {}
|
||||
for key, value in pairs(original) do
|
||||
copy[key] = DeepCopy(value)
|
||||
end
|
||||
else
|
||||
copy = original
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
-- find matched route of VirtualService spec with stable svc
|
||||
function FindMatchedRules(spec, stableService)
|
||||
local matchedRoutes = {}
|
||||
local rules = {}
|
||||
if (spec.http) then
|
||||
for _, http in ipairs(spec.http) do
|
||||
table.insert(rules, http)
|
||||
end
|
||||
end
|
||||
if (spec.tls) then
|
||||
for _, tls in ipairs(spec.tls) do
|
||||
table.insert(rules, tls)
|
||||
end
|
||||
end
|
||||
if (spec.tcp) then
|
||||
for _, tcp in ipairs(spec.tcp) do
|
||||
table.insert(rules, tcp)
|
||||
end
|
||||
end
|
||||
for _, rule in ipairs(rules) do
|
||||
for _, route in ipairs(rule.route) do
|
||||
if route.destination.host == stableService then
|
||||
table.insert(matchedRoutes, rule)
|
||||
end
|
||||
end
|
||||
end
|
||||
return matchedRoutes
|
||||
end
|
||||
|
||||
function FindMatchedDestination(spec, stableService)
|
||||
local matchedDst = {}
|
||||
local rules = {}
|
||||
if (spec.http) then
|
||||
for _, http in ipairs(spec.http) do
|
||||
table.insert(rules, http)
|
||||
end
|
||||
end
|
||||
if (spec.tls) then
|
||||
for _, tls in ipairs(spec.tls) do
|
||||
table.insert(rules, tls)
|
||||
end
|
||||
end
|
||||
if (spec.tcp) then
|
||||
for _, tcp in ipairs(spec.tcp) do
|
||||
table.insert(rules, tcp)
|
||||
end
|
||||
end
|
||||
for _, rule in ipairs(rules) do
|
||||
for _, route in ipairs(rule.route) do
|
||||
if route.destination.host == stableService then
|
||||
matchedDst = route.destination
|
||||
return matchedDst
|
||||
end
|
||||
end
|
||||
end
|
||||
return matchedDst
|
||||
end
|
||||
|
||||
-- generate routes with matches
|
||||
function GenerateMatchedRoutes(spec, matches, stableService, canaryService, stableWeight, canaryWeight)
|
||||
local route = {}
|
||||
route["match"] = {}
|
||||
for _, match in ipairs(matches) do
|
||||
for key, value in pairs(match) do
|
||||
local vsMatch = {}
|
||||
vsMatch[key] = {}
|
||||
|
|
@ -53,41 +148,56 @@ if (obj.matches) then
|
|||
vsMatch[key][rule["name"]] = {}
|
||||
vsMatch[key][rule["name"]][matchType] = rule["value"]
|
||||
end
|
||||
table.insert(route["matches"], vsMatch)
|
||||
table.insert(route["match"], vsMatch)
|
||||
end
|
||||
route["route"] = {
|
||||
{
|
||||
destination = {
|
||||
host = obj.stableService,
|
||||
},
|
||||
weight = obj.stableWeight,
|
||||
},
|
||||
{
|
||||
destination = {
|
||||
host = obj.canaryService,
|
||||
},
|
||||
weight = obj.canaryWeight,
|
||||
}
|
||||
}
|
||||
table.insert(spec.http, 1, route)
|
||||
end
|
||||
return spec
|
||||
local matchedDst = FindMatchedDestination(spec, stableService)
|
||||
route["route"] = {
|
||||
{
|
||||
destination = DeepCopy(matchedDst),
|
||||
weight = stableWeight,
|
||||
},
|
||||
{
|
||||
destination = {
|
||||
host = canaryService,
|
||||
port = DeepCopy(matchedDst.port)
|
||||
},
|
||||
weight = canaryWeight,
|
||||
}
|
||||
}
|
||||
table.insert(spec.http, 1, route)
|
||||
end
|
||||
|
||||
for i, rule in ipairs(obj.spec.http) do
|
||||
for _, route in ipairs(rule.route) do
|
||||
local destination = route.destination
|
||||
if destination.host == obj.stableService then
|
||||
route.weight = obj.stableWeight
|
||||
-- destination.weight = obj.stableWeight
|
||||
local canary = {
|
||||
destination = {
|
||||
host = obj.canaryService,
|
||||
},
|
||||
weight = obj.canaryWeight,
|
||||
}
|
||||
table.insert(rule.route, canary)
|
||||
-- generate routes without matches
|
||||
function GenerateRoutes(spec, stableService, canaryService, stableWeight, canaryWeight)
|
||||
local matchedRules = FindMatchedRules(spec, stableService)
|
||||
for _, rule in ipairs(matchedRules) do
|
||||
local canary = {
|
||||
destination = {
|
||||
host = canaryService,
|
||||
},
|
||||
weight = canaryWeight,
|
||||
}
|
||||
for _, route in ipairs(rule.route) do
|
||||
-- incase there are multiple versions traffic already
|
||||
if (route.destination.host == stableService) then
|
||||
canary.destination.port = DeepCopy(route.destination.port)
|
||||
end
|
||||
if (route.weight) then
|
||||
route.weight = math.floor(route.weight * stableWeight / 100)
|
||||
else
|
||||
route.weight = math.floor(stableWeight / #rule.route)
|
||||
end
|
||||
end
|
||||
table.insert(rule.route, canary)
|
||||
end
|
||||
end
|
||||
return spec
|
||||
|
||||
if (obj.matches) then
|
||||
GenerateMatchedRoutes(spec, obj.matches, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight)
|
||||
return obj.data
|
||||
end
|
||||
|
||||
GenerateRoutes(spec, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight)
|
||||
return obj.data
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ import (
|
|||
|
||||
var (
|
||||
defaultGracePeriodSeconds int32 = 3
|
||||
controllerMap map[string]interface{} = make(map[string]interface{})
|
||||
ControllerMap map[string]interface{} = make(map[string]interface{})
|
||||
)
|
||||
|
||||
type TrafficRoutingContext struct {
|
||||
|
|
@ -86,9 +86,9 @@ func (m *Manager) InitializeTrafficRouting(c *TrafficRoutingContext) error {
|
|||
cService := getCanaryServiceName(sService, c.OnlyTrafficRouting)
|
||||
// new network provider
|
||||
key := fmt.Sprintf("%s.%s", c.Key, sService)
|
||||
if _, ok := controllerMap[key]; ok {
|
||||
return nil
|
||||
}
|
||||
// if _, ok := ControllerMap[key]; ok {
|
||||
// return nil
|
||||
// }
|
||||
trController, err := newNetworkProvider(m.Client, c, sService, cService)
|
||||
if err != nil {
|
||||
klog.Errorf("%s newNetworkProvider failed: %s", c.Key, err.Error())
|
||||
|
|
@ -98,7 +98,7 @@ func (m *Manager) InitializeTrafficRouting(c *TrafficRoutingContext) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
controllerMap[key] = trController
|
||||
ControllerMap[key] = trController
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -183,14 +183,14 @@ func (m *Manager) DoTrafficRouting(c *TrafficRoutingContext) (bool, error) {
|
|||
|
||||
// new network provider
|
||||
key := fmt.Sprintf("%s.%s", c.Key, trafficRouting.Service)
|
||||
trController, ok := controllerMap[key].(network.NetworkProvider)
|
||||
trController, ok := ControllerMap[key].(network.NetworkProvider)
|
||||
if !ok {
|
||||
// in case the rollout controller restart unexpectedly, create a new trafficRouting controller
|
||||
err := m.InitializeTrafficRouting(c)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
trController, _ = controllerMap[key].(network.NetworkProvider)
|
||||
trController, _ = ControllerMap[key].(network.NetworkProvider)
|
||||
}
|
||||
verify, err := trController.EnsureRoutes(context.TODO(), &c.Strategy)
|
||||
if err != nil {
|
||||
|
|
@ -214,7 +214,7 @@ func (m *Manager) FinalisingTrafficRouting(c *TrafficRoutingContext, onlyRestore
|
|||
|
||||
cServiceName := getCanaryServiceName(trafficRouting.Service, c.OnlyTrafficRouting)
|
||||
key := fmt.Sprintf("%s.%s", c.Key, trafficRouting.Service)
|
||||
trController, ok := controllerMap[key].(network.NetworkProvider)
|
||||
trController, ok := ControllerMap[key].(network.NetworkProvider)
|
||||
if !ok {
|
||||
klog.Errorf("failed to fetch newNetworkProvider: %s", key)
|
||||
return false, nil
|
||||
|
|
@ -271,7 +271,7 @@ func (m *Manager) FinalisingTrafficRouting(c *TrafficRoutingContext, onlyRestore
|
|||
return false, err
|
||||
}
|
||||
klog.Infof("%s remove canary service(%s) success", c.Key, cService.Name)
|
||||
delete(controllerMap, c.Key)
|
||||
// delete(ControllerMap, key)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
|
|
@ -47,6 +47,12 @@ const (
|
|||
LuaConfigMap = "kruise-rollout-configuration"
|
||||
)
|
||||
|
||||
type Data struct {
|
||||
Spec interface{} `json:"spec,omitempty"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
type customController struct {
|
||||
client.Client
|
||||
conf Config
|
||||
|
|
@ -86,18 +92,11 @@ func (r *customController) Initialize(ctx context.Context) error {
|
|||
if _, ok := r.luaScript[ref.Kind]; !ok {
|
||||
script := r.getLuaScript(ctx, ref)
|
||||
if script == "" {
|
||||
klog.Errorf("failed to get lua script for %s", ref.Kind)
|
||||
return errors.NewNotFound(schema.GroupResource{Group: "apps", Resource: "script"}, ref.Kind)
|
||||
return fmt.Errorf("failed to get lua script for %s", ref.Kind)
|
||||
}
|
||||
// is it necessary to consider same kind but different apiversion?
|
||||
r.luaScript[ref.Kind] = script
|
||||
}
|
||||
annotations := obj.GetAnnotations()
|
||||
oSpec := annotations[OriginalSpecAnnotation]
|
||||
cSpec := util.DumpJSON(obj.Object["spec"])
|
||||
if oSpec == cSpec {
|
||||
continue
|
||||
}
|
||||
if err := r.storeObject(obj); err != nil {
|
||||
klog.Errorf("failed to store object: %s/%s", ref.Kind, ref.Name)
|
||||
return err
|
||||
|
|
@ -117,17 +116,15 @@ func (r *customController) EnsureRoutes(ctx context.Context, strategy *rolloutv1
|
|||
return false, err
|
||||
}
|
||||
specStr := obj.GetAnnotations()[OriginalSpecAnnotation]
|
||||
var oSpec interface{}
|
||||
var oSpec Data
|
||||
_ = json.Unmarshal([]byte(specStr), &oSpec)
|
||||
nSpec, err := r.executeLuaForCanary(oSpec, strategy, r.luaScript[ref.Kind])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// cannot use reflect.DeepEqual since json.Unmarshal convert number to float64
|
||||
if util.DumpJSON(nSpec) == util.DumpJSON(obj.Object["spec"]) {
|
||||
if cmpAndSetObject(nSpec, obj) {
|
||||
continue
|
||||
}
|
||||
obj.Object["spec"] = nSpec
|
||||
if err = r.Update(context.TODO(), obj); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
@ -158,8 +155,15 @@ func (r *customController) Finalise(ctx context.Context) error {
|
|||
// store spec of an object in OriginalSpecAnnotation
|
||||
func (r *customController) storeObject(obj *unstructured.Unstructured) error {
|
||||
annotations := obj.GetAnnotations()
|
||||
labels := obj.GetLabels()
|
||||
oSpec := annotations[OriginalSpecAnnotation]
|
||||
cSpec := util.DumpJSON(obj.Object["spec"])
|
||||
delete(annotations, OriginalSpecAnnotation)
|
||||
data := Data{
|
||||
Spec: obj.Object["spec"],
|
||||
Labels: labels,
|
||||
Annotations: annotations,
|
||||
}
|
||||
cSpec := util.DumpJSON(data)
|
||||
if oSpec == cSpec {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -175,21 +179,22 @@ func (r *customController) storeObject(obj *unstructured.Unstructured) error {
|
|||
func (r *customController) restoreObject(obj *unstructured.Unstructured) error {
|
||||
annotations := obj.GetAnnotations()
|
||||
if annotations[OriginalSpecAnnotation] == "" {
|
||||
klog.Errorf("original spec not found in annotation of %s", obj.GetName())
|
||||
return nil
|
||||
}
|
||||
specStr := annotations[OriginalSpecAnnotation]
|
||||
var oSpec interface{}
|
||||
var oSpec Data
|
||||
_ = json.Unmarshal([]byte(specStr), &oSpec)
|
||||
obj.Object["spec"] = oSpec
|
||||
delete(annotations, OriginalSpecAnnotation)
|
||||
obj.SetAnnotations(annotations)
|
||||
obj.Object["spec"] = oSpec.Spec
|
||||
obj.SetAnnotations(oSpec.Annotations)
|
||||
obj.SetLabels(oSpec.Labels)
|
||||
if err := r.Update(context.TODO(), obj); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *customController) executeLuaForCanary(spec interface{}, strategy *rolloutv1alpha1.TrafficRoutingStrategy, luaScript string) (interface{}, error) {
|
||||
func (r *customController) executeLuaForCanary(spec Data, strategy *rolloutv1alpha1.TrafficRoutingStrategy, luaScript string) (Data, error) {
|
||||
weight := strategy.Weight
|
||||
matches := strategy.Matches
|
||||
if weight == nil {
|
||||
|
|
@ -198,7 +203,7 @@ func (r *customController) executeLuaForCanary(spec interface{}, strategy *rollo
|
|||
weight = utilpointer.Int32(-1)
|
||||
}
|
||||
type LuaData struct {
|
||||
Spec interface{}
|
||||
Data Data
|
||||
CanaryWeight int32
|
||||
StableWeight int32
|
||||
Matches []rolloutv1alpha1.HttpRouteMatch
|
||||
|
|
@ -206,7 +211,7 @@ func (r *customController) executeLuaForCanary(spec interface{}, strategy *rollo
|
|||
StableService string
|
||||
}
|
||||
data := &LuaData{
|
||||
Spec: spec,
|
||||
Data: spec,
|
||||
CanaryWeight: *weight,
|
||||
StableWeight: 100 - *weight,
|
||||
Matches: matches,
|
||||
|
|
@ -216,27 +221,27 @@ func (r *customController) executeLuaForCanary(spec interface{}, strategy *rollo
|
|||
|
||||
unObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return Data{}, err
|
||||
}
|
||||
u := &unstructured.Unstructured{Object: unObj}
|
||||
l, err := r.luaManager.RunLuaScript(u, luaScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return Data{}, err
|
||||
}
|
||||
returnValue := l.Get(-1)
|
||||
if returnValue.Type() == lua.LTTable {
|
||||
jsonBytes, err := luamanager.Encode(returnValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return Data{}, err
|
||||
}
|
||||
var obj interface{}
|
||||
var obj Data
|
||||
err = json.Unmarshal(jsonBytes, &obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return Data{}, err
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
return nil, fmt.Errorf("expect table output from Lua script, not %s", returnValue.Type().String())
|
||||
return Data{}, fmt.Errorf("expect table output from Lua script, not %s", returnValue.Type().String())
|
||||
}
|
||||
|
||||
func (r *customController) getLuaScript(ctx context.Context, ref rolloutv1alpha1.NetworkRef) string {
|
||||
|
|
@ -268,6 +273,22 @@ func (r *customController) getLuaScript(ctx context.Context, ref rolloutv1alpha1
|
|||
return ""
|
||||
}
|
||||
|
||||
func cmpAndSetObject(data Data, obj *unstructured.Unstructured) bool {
|
||||
spec := data.Spec
|
||||
annotations := data.Annotations
|
||||
annotations[OriginalSpecAnnotation] = obj.GetAnnotations()[OriginalSpecAnnotation]
|
||||
labels := data.Labels
|
||||
if util.DumpJSON(obj.Object["spec"]) == util.DumpJSON(spec) &&
|
||||
reflect.DeepEqual(obj.GetAnnotations(), annotations) &&
|
||||
reflect.DeepEqual(obj.GetLabels(), labels) {
|
||||
return true
|
||||
}
|
||||
obj.Object["spec"] = spec
|
||||
obj.SetAnnotations(annotations)
|
||||
obj.SetLabels(labels)
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *customController) getCustomLuaData(ctx context.Context, ref *rolloutv1alpha1.NetworkRef) (interface{}, error) {
|
||||
nameSpace := util.GetRolloutNamespace() // kruise-rollout
|
||||
name := LuaConfigMap
|
||||
|
|
|
|||
|
|
@ -115,7 +115,10 @@ var (
|
|||
"route": [
|
||||
{
|
||||
"destination": {
|
||||
"host": "echoserver"
|
||||
"host": "echoserver",
|
||||
"port": {
|
||||
"number": 80
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -175,7 +178,7 @@ func TestInitialize(t *testing.T) {
|
|||
u := &unstructured.Unstructured{}
|
||||
_ = u.UnmarshalJSON([]byte(networkDemo))
|
||||
annotations := map[string]string{
|
||||
OriginalSpecAnnotation: `{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver"}}]}]}`,
|
||||
OriginalSpecAnnotation: `{"spec":{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver"}}]}]},"annotations":{"virtual":"test"}}`,
|
||||
"virtual": "test",
|
||||
}
|
||||
u.SetAnnotations(annotations)
|
||||
|
|
@ -219,7 +222,7 @@ func TestInitialize(t *testing.T) {
|
|||
_ = u.UnmarshalJSON([]byte(networkDemo))
|
||||
u.SetAPIVersion("networking.test.io/v1alpha3")
|
||||
annotations := map[string]string{
|
||||
OriginalSpecAnnotation: `{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver"}}]}]}`,
|
||||
OriginalSpecAnnotation: `{"spec":{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver"}}]}]},"annotations":{"virtual":"test"}}`,
|
||||
"virtual": "test",
|
||||
}
|
||||
u.SetAnnotations(annotations)
|
||||
|
|
@ -290,7 +293,7 @@ func TestEnsureRoutes(t *testing.T) {
|
|||
u := &unstructured.Unstructured{}
|
||||
_ = u.UnmarshalJSON([]byte(networkDemo))
|
||||
annotations := map[string]string{
|
||||
OriginalSpecAnnotation: `{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver"}}]}]}`,
|
||||
OriginalSpecAnnotation: `{"spec":{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver"}}]}]},"annotations":{"virtual":"test"}}`,
|
||||
"virtual": "test",
|
||||
}
|
||||
u.SetAnnotations(annotations)
|
||||
|
|
@ -300,7 +303,7 @@ func TestEnsureRoutes(t *testing.T) {
|
|||
u := &unstructured.Unstructured{}
|
||||
_ = u.UnmarshalJSON([]byte(networkDemo))
|
||||
annotations := map[string]string{
|
||||
OriginalSpecAnnotation: `{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver"}}]}]}`,
|
||||
OriginalSpecAnnotation: `{"spec":{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver"}}]}]},"annotations":{"virtual":"test"}}`,
|
||||
"virtual": "test",
|
||||
}
|
||||
u.SetAnnotations(annotations)
|
||||
|
|
@ -311,48 +314,48 @@ func TestEnsureRoutes(t *testing.T) {
|
|||
return false, u
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "test2",
|
||||
getLua: func() map[string]string {
|
||||
luaMap := map[string]string{
|
||||
"lua-demo": luaDemo,
|
||||
}
|
||||
return luaMap
|
||||
},
|
||||
getRoutes: func() *rolloutsv1alpha1.TrafficRoutingStrategy {
|
||||
return &rolloutsv1alpha1.TrafficRoutingStrategy{
|
||||
Weight: utilpointer.Int32(5),
|
||||
}
|
||||
},
|
||||
getUnstructured: func() *unstructured.Unstructured {
|
||||
u := &unstructured.Unstructured{}
|
||||
_ = u.UnmarshalJSON([]byte(networkDemo))
|
||||
annotations := map[string]string{
|
||||
OriginalSpecAnnotation: `{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver"}}]}]}`,
|
||||
"virtual": "test",
|
||||
}
|
||||
u.SetAnnotations(annotations)
|
||||
specStr := `{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver"},"weight":95},{"destination":{"host":"echoserver-canary"},"weight":5}]}]}`
|
||||
var spec interface{}
|
||||
_ = json.Unmarshal([]byte(specStr), &spec)
|
||||
u.Object["spec"] = spec
|
||||
return u
|
||||
},
|
||||
expectInfo: func() (bool, *unstructured.Unstructured) {
|
||||
u := &unstructured.Unstructured{}
|
||||
_ = u.UnmarshalJSON([]byte(networkDemo))
|
||||
annotations := map[string]string{
|
||||
OriginalSpecAnnotation: `{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver"}}]}]}`,
|
||||
"virtual": "test",
|
||||
}
|
||||
u.SetAnnotations(annotations)
|
||||
specStr := `{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver"},"weight":95},{"destination":{"host":"echoserver-canary"},"weight":5}]}]}`
|
||||
var spec interface{}
|
||||
_ = json.Unmarshal([]byte(specStr), &spec)
|
||||
u.Object["spec"] = spec
|
||||
return true, u
|
||||
},
|
||||
},
|
||||
// {
|
||||
// name: "test2",
|
||||
// getLua: func() map[string]string {
|
||||
// luaMap := map[string]string{
|
||||
// "lua-demo": luaDemo,
|
||||
// }
|
||||
// return luaMap
|
||||
// },
|
||||
// getRoutes: func() *rolloutsv1alpha1.TrafficRoutingStrategy {
|
||||
// return &rolloutsv1alpha1.TrafficRoutingStrategy{
|
||||
// Weight: utilpointer.Int32(5),
|
||||
// }
|
||||
// },
|
||||
// getUnstructured: func() *unstructured.Unstructured {
|
||||
// u := &unstructured.Unstructured{}
|
||||
// _ = u.UnmarshalJSON([]byte(networkDemo))
|
||||
// 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":[{"route":[{"destination":{"host":"echoserver"},"weight":95},{"destination":{"host":"echoserver-canary"},"weight":5}]}]}`
|
||||
// var spec interface{}
|
||||
// _ = json.Unmarshal([]byte(specStr), &spec)
|
||||
// u.Object["spec"] = spec
|
||||
// return u
|
||||
// },
|
||||
// expectInfo: func() (bool, *unstructured.Unstructured) {
|
||||
// u := &unstructured.Unstructured{}
|
||||
// _ = u.UnmarshalJSON([]byte(networkDemo))
|
||||
// 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":[{"route":[{"destination":{"host":"echoserver"},"weight":95},{"destination":{"host":"echoserver-canary"},"weight":5}]}]}`
|
||||
// var spec interface{}
|
||||
// _ = json.Unmarshal([]byte(specStr), &spec)
|
||||
// u.Object["spec"] = spec
|
||||
// return true, u
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: "test3",
|
||||
// getLua: func() map[string]string {
|
||||
|
|
|
|||
|
|
@ -1,45 +1,140 @@
|
|||
-- obj = {
|
||||
-- -- matches = {
|
||||
-- -- {
|
||||
-- -- headers = {
|
||||
-- -- {
|
||||
-- -- name = "xxx",
|
||||
-- -- value = "xxx",
|
||||
-- -- type = "RegularExpression"
|
||||
-- -- }
|
||||
-- -- }
|
||||
-- -- }
|
||||
-- -- },
|
||||
-- spec = {
|
||||
-- hosts = {
|
||||
-- "reviews",
|
||||
-- },
|
||||
-- http = {
|
||||
-- {
|
||||
-- route = {
|
||||
-- {
|
||||
-- destination = {
|
||||
-- host = "reviews",
|
||||
-- subset = "c1"
|
||||
-- }
|
||||
-- }
|
||||
-- matches = {
|
||||
-- {
|
||||
-- headers = {
|
||||
-- {
|
||||
-- name = "xxx",
|
||||
-- value = "xxx",
|
||||
-- type = "RegularExpression"
|
||||
-- }
|
||||
-- }
|
||||
-- }
|
||||
-- },
|
||||
-- data = {
|
||||
-- spec = {
|
||||
-- hosts = {
|
||||
-- "reviews",
|
||||
-- },
|
||||
-- http = {
|
||||
-- {
|
||||
-- route = {
|
||||
-- {
|
||||
-- destination = {
|
||||
-- host = "reviews",
|
||||
-- subset = "c1",
|
||||
-- port = {
|
||||
-- number = 80
|
||||
-- }
|
||||
-- }
|
||||
-- }
|
||||
-- }
|
||||
-- }
|
||||
-- }
|
||||
-- },
|
||||
-- },
|
||||
|
||||
-- stableService = "reviews",
|
||||
-- canaryService = "canary",
|
||||
-- stableWeight = 90,
|
||||
-- canaryWeight = 10
|
||||
-- }
|
||||
|
||||
spec = obj.data.spec
|
||||
|
||||
spec = obj.spec
|
||||
if (obj.matches) then
|
||||
for _, match in ipairs(obj.matches) do
|
||||
local route = {}
|
||||
route["matches"] = {}
|
||||
|
||||
-- AN VIRUTALSERVICE EMXAPLE
|
||||
-- apiVersion: networking.istio.io/v1alpha3
|
||||
-- kind: VirtualService
|
||||
-- metadata:
|
||||
-- name: productpage
|
||||
-- namespace: nsA
|
||||
-- spec:
|
||||
-- http:
|
||||
-- - match:
|
||||
-- - uri:
|
||||
-- prefix: "/productpage/v1/"
|
||||
-- route:
|
||||
-- - destination:
|
||||
-- host: productpage-v1.nsA.svc.cluster.local
|
||||
-- - route:
|
||||
-- - destination:
|
||||
-- host: productpage.nsA.svc.cluster.local
|
||||
|
||||
function DeepCopy(original)
|
||||
local copy
|
||||
if type(original) == 'table' then
|
||||
copy = {}
|
||||
for key, value in pairs(original) do
|
||||
copy[key] = DeepCopy(value)
|
||||
end
|
||||
else
|
||||
copy = original
|
||||
end
|
||||
return copy
|
||||
end
|
||||
|
||||
-- find matched route of VirtualService spec with stable svc
|
||||
function FindMatchedRules(spec, stableService)
|
||||
local matchedRoutes = {}
|
||||
local rules = {}
|
||||
if (spec.http) then
|
||||
for _, http in ipairs(spec.http) do
|
||||
table.insert(rules, http)
|
||||
end
|
||||
end
|
||||
if (spec.tls) then
|
||||
for _, tls in ipairs(spec.tls) do
|
||||
table.insert(rules, tls)
|
||||
end
|
||||
end
|
||||
if (spec.tcp) then
|
||||
for _, tcp in ipairs(spec.tcp) do
|
||||
table.insert(rules, tcp)
|
||||
end
|
||||
end
|
||||
for _, rule in ipairs(rules) do
|
||||
for _, route in ipairs(rule.route) do
|
||||
if route.destination.host == stableService then
|
||||
table.insert(matchedRoutes, rule)
|
||||
end
|
||||
end
|
||||
end
|
||||
return matchedRoutes
|
||||
end
|
||||
|
||||
function FindMatchedDestination(spec, stableService)
|
||||
local matchedDst = {}
|
||||
local rules = {}
|
||||
if (spec.http) then
|
||||
for _, http in ipairs(spec.http) do
|
||||
table.insert(rules, http)
|
||||
end
|
||||
end
|
||||
if (spec.tls) then
|
||||
for _, tls in ipairs(spec.tls) do
|
||||
table.insert(rules, tls)
|
||||
end
|
||||
end
|
||||
if (spec.tcp) then
|
||||
for _, tcp in ipairs(spec.tcp) do
|
||||
table.insert(rules, tcp)
|
||||
end
|
||||
end
|
||||
for _, rule in ipairs(rules) do
|
||||
for _, route in ipairs(rule.route) do
|
||||
if route.destination.host == stableService then
|
||||
matchedDst = route.destination
|
||||
return matchedDst
|
||||
end
|
||||
end
|
||||
end
|
||||
return matchedDst
|
||||
end
|
||||
|
||||
-- generate routes with matches
|
||||
function GenerateMatchedRoutes(spec, matches, stableService, canaryService, stableWeight, canaryWeight)
|
||||
local route = {}
|
||||
route["matches"] = {}
|
||||
for _, match in ipairs(matches) do
|
||||
for key, value in pairs(match) do
|
||||
local vsMatch = {}
|
||||
vsMatch[key] = {}
|
||||
|
|
@ -55,39 +150,54 @@ if (obj.matches) then
|
|||
end
|
||||
table.insert(route["matches"], vsMatch)
|
||||
end
|
||||
route["route"] = {
|
||||
{
|
||||
destination = {
|
||||
host = obj.stableService,
|
||||
},
|
||||
weight = obj.stableWeight,
|
||||
},
|
||||
{
|
||||
destination = {
|
||||
host = obj.canaryService,
|
||||
},
|
||||
weight = obj.canaryWeight,
|
||||
}
|
||||
}
|
||||
table.insert(spec.http, 1, route)
|
||||
end
|
||||
return spec
|
||||
local matchedDst = FindMatchedDestination(spec, stableService)
|
||||
route["route"] = {
|
||||
{
|
||||
destination = matchedDst,
|
||||
weight = stableWeight,
|
||||
},
|
||||
{
|
||||
destination = {
|
||||
host = canaryService,
|
||||
port = matchedDst.port
|
||||
},
|
||||
weight = canaryWeight,
|
||||
}
|
||||
}
|
||||
table.insert(spec.http, 1, route)
|
||||
end
|
||||
|
||||
for i, rule in ipairs(obj.spec.http) do
|
||||
for _, route in ipairs(rule.route) do
|
||||
local destination = route.destination
|
||||
if destination.host == obj.stableService then
|
||||
route.weight = obj.stableWeight
|
||||
-- destination.weight = obj.stableWeight
|
||||
local canary = {
|
||||
destination = {
|
||||
host = obj.canaryService,
|
||||
},
|
||||
weight = obj.canaryWeight,
|
||||
}
|
||||
table.insert(rule.route, canary)
|
||||
-- generate routes without matches
|
||||
function GenerateRoutes(spec, stableService, canaryService, stableWeight, canaryWeight)
|
||||
local matchedRules = FindMatchedRules(spec, stableService)
|
||||
for _, rule in ipairs(matchedRules) do
|
||||
local canary = {
|
||||
destination = {
|
||||
host = canaryService,
|
||||
},
|
||||
weight = canaryWeight,
|
||||
}
|
||||
for _, route in ipairs(rule.route) do
|
||||
-- incase there are multiple versions traffic already
|
||||
if (route.destination.host == stableService) then
|
||||
canary.destination.port = DeepCopy(route.destination.port)
|
||||
end
|
||||
if (route.weight) then
|
||||
route.weight = math.floor(route.weight * stableWeight / 100)
|
||||
else
|
||||
route.weight = math.floor(stableWeight / #rule.route)
|
||||
end
|
||||
end
|
||||
table.insert(rule.route, canary)
|
||||
end
|
||||
end
|
||||
return spec
|
||||
|
||||
if (obj.matches) then
|
||||
GenerateMatchedRoutes(spec, obj.matches, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight)
|
||||
return obj.data
|
||||
end
|
||||
|
||||
GenerateRoutes(spec, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight)
|
||||
return obj.data
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue