generate istio destinationRule subset from pod-template-hash && add fields to allow user set canary/stable subset name

Signed-off-by: yunbo <yunbo10124scut@gmail.com>
This commit is contained in:
yunbo 2024-04-09 19:14:30 +08:00
parent 07c1731e8a
commit 86c32e4941
19 changed files with 375 additions and 98 deletions

View File

@ -126,6 +126,7 @@ func (src *Rollout) ConvertTo(dst conversion.Hub) error {
func ConversionToV1beta1TrafficRoutingRef(src TrafficRoutingRef) (dst v1beta1.TrafficRoutingRef) { func ConversionToV1beta1TrafficRoutingRef(src TrafficRoutingRef) (dst v1beta1.TrafficRoutingRef) {
dst.Service = src.Service dst.Service = src.Service
dst.GracePeriodSeconds = src.GracePeriodSeconds dst.GracePeriodSeconds = src.GracePeriodSeconds
dst.AdditionalParams = src.AdditionalParams
if src.Ingress != nil { if src.Ingress != nil {
dst.Ingress = &v1beta1.IngressTrafficRouting{ dst.Ingress = &v1beta1.IngressTrafficRouting{
ClassType: src.Ingress.ClassType, ClassType: src.Ingress.ClassType,

View File

@ -23,6 +23,8 @@ import (
const ( const (
ProgressingRolloutFinalizerPrefix = "progressing.rollouts.kruise.io" ProgressingRolloutFinalizerPrefix = "progressing.rollouts.kruise.io"
IstioStableSubsetName = "istio.destinationRule.stableSubsetName"
IstioCanarySubsetName = "istio.destinationRule.canarySubsetName"
) )
// TrafficRoutingRef hosts all the different configuration for supported service meshes to enable more fine-grained traffic routing // TrafficRoutingRef hosts all the different configuration for supported service meshes to enable more fine-grained traffic routing
@ -38,6 +40,10 @@ type TrafficRoutingRef struct {
Gateway *GatewayTrafficRouting `json:"gateway,omitempty"` Gateway *GatewayTrafficRouting `json:"gateway,omitempty"`
// CustomNetworkRefs hold a list of custom providers to route traffic // CustomNetworkRefs hold a list of custom providers to route traffic
CustomNetworkRefs []CustomNetworkRef `json:"customNetworkRefs,omitempty"` CustomNetworkRefs []CustomNetworkRef `json:"customNetworkRefs,omitempty"`
// vaild keys:
// + IstioStableSubsetName
// + IstioCanarySubsetName
AdditionalParams map[string]string `json:"additionalParams,omitempty"`
} }
// IngressTrafficRouting configuration for ingress controller to control traffic routing // IngressTrafficRouting configuration for ingress controller to control traffic routing

View File

@ -16,6 +16,11 @@ limitations under the License.
package v1beta1 package v1beta1
const (
IstioStableSubsetName = "istio.destinationRule.stableSubsetName"
IstioCanarySubsetName = "istio.destinationRule.canarySubsetName"
)
// TrafficRoutingRef hosts all the different configuration for supported service meshes to enable more fine-grained traffic routing // TrafficRoutingRef hosts all the different configuration for supported service meshes to enable more fine-grained traffic routing
type TrafficRoutingRef struct { type TrafficRoutingRef struct {
// Service holds the name of a service which selects pods with stable version and don't select any pods with canary version. // Service holds the name of a service which selects pods with stable version and don't select any pods with canary version.
@ -29,6 +34,10 @@ type TrafficRoutingRef struct {
Gateway *GatewayTrafficRouting `json:"gateway,omitempty"` Gateway *GatewayTrafficRouting `json:"gateway,omitempty"`
// CustomNetworkRefs hold a list of custom providers to route traffic // CustomNetworkRefs hold a list of custom providers to route traffic
CustomNetworkRefs []ObjectRef `json:"customNetworkRefs,omitempty"` CustomNetworkRefs []ObjectRef `json:"customNetworkRefs,omitempty"`
// vaild keys:
// + IstioStableSubsetName
// + IstioCanarySubsetName
AdditionalParams map[string]string `json:"additionalParams,omitempty"`
} }
// IngressTrafficRouting configuration for ingress controller to control traffic routing // IngressTrafficRouting configuration for ingress controller to control traffic routing

View File

@ -344,6 +344,11 @@ spec:
for supported service meshes to enable more fine-grained for supported service meshes to enable more fine-grained
traffic routing traffic routing
properties: properties:
additionalParams:
additionalProperties:
type: string
description: 'vaild keys:'
type: object
customNetworkRefs: customNetworkRefs:
description: CustomNetworkRefs hold a list of custom description: CustomNetworkRefs hold a list of custom
providers to route traffic providers to route traffic
@ -834,6 +839,11 @@ spec:
for supported service meshes to enable more fine-grained for supported service meshes to enable more fine-grained
traffic routing traffic routing
properties: properties:
additionalParams:
additionalProperties:
type: string
description: 'vaild keys:'
type: object
customNetworkRefs: customNetworkRefs:
description: CustomNetworkRefs hold a list of custom description: CustomNetworkRefs hold a list of custom
providers to route traffic providers to route traffic

View File

@ -54,6 +54,11 @@ spec:
for supported service meshes to enable more fine-grained traffic for supported service meshes to enable more fine-grained traffic
routing routing
properties: properties:
additionalParams:
additionalProperties:
type: string
description: 'vaild keys:'
type: object
customNetworkRefs: customNetworkRefs:
description: CustomNetworkRefs hold a list of custom providers description: CustomNetworkRefs hold a list of custom providers
to route traffic to route traffic

View File

@ -100,11 +100,14 @@ func objectToTable(path string) error {
Annotations: testCase.Original.GetAnnotations(), Annotations: testCase.Original.GetAnnotations(),
Spec: testCase.Original.Object["spec"], Spec: testCase.Original.Object["spec"],
}, },
Matches: step.TrafficRoutingStrategy.Matches, Matches: step.TrafficRoutingStrategy.Matches,
CanaryWeight: *weight, CanaryWeight: *weight,
StableWeight: 100 - *weight, StableWeight: 100 - *weight,
CanaryService: canaryService, CanaryService: canaryService,
StableService: stableService, StableService: stableService,
StableRevision: "podtemplatehash-v1",
CanaryRevision: "podtemplatehash-v2",
RevisionLabelKey: "pod-template-hash",
} }
uList[fmt.Sprintf("step_%d", i)] = data uList[fmt.Sprintf("step_%d", i)] = data
} }
@ -128,11 +131,14 @@ func objectToTable(path string) error {
Annotations: testCase.Original.GetAnnotations(), Annotations: testCase.Original.GetAnnotations(),
Spec: testCase.Original.Object["spec"], Spec: testCase.Original.Object["spec"],
}, },
Matches: matches, Matches: matches,
CanaryWeight: *weight, CanaryWeight: *weight,
StableWeight: 100 - *weight, StableWeight: 100 - *weight,
CanaryService: canaryService, CanaryService: canaryService,
StableService: stableService, StableService: stableService,
StableRevision: "podtemplatehash-v1",
CanaryRevision: "podtemplatehash-v2",
RevisionLabelKey: "pod-template-hash",
} }
uList["steps_0"] = data uList["steps_0"] = data
} else { } else {

View File

@ -16,6 +16,9 @@ trafficRouting:
- apiVersion: networking.istio.io/v1beta1 - apiVersion: networking.istio.io/v1beta1
kind: DestinationRule kind: DestinationRule
name: ds-demo name: ds-demo
additionalParams:
istio.destinationRule.stableSubsetName: "version-base"
istio.destinationRule.canarySubsetName: "canary"
original: original:
apiVersion: networking.istio.io/v1beta1 apiVersion: networking.istio.io/v1beta1
kind: DestinationRule kind: DestinationRule
@ -27,9 +30,7 @@ original:
loadBalancer: loadBalancer:
simple: ROUND_ROBIN simple: ROUND_ROBIN
subsets: subsets:
- labels: - name: version-base
version: base
name: version-base
expected: expected:
- apiVersion: networking.istio.io/v1beta1 - apiVersion: networking.istio.io/v1beta1
kind: DestinationRule kind: DestinationRule
@ -42,8 +43,8 @@ expected:
simple: ROUND_ROBIN simple: ROUND_ROBIN
subsets: subsets:
- labels: - labels:
version: base pod-template-hash: "podtemplatehash-v1"
name: version-base name: version-base
- labels: - labels:
istio.service.tag: gray pod-template-hash: "podtemplatehash-v2"
name: canary name: canary

View File

@ -1,8 +1,51 @@
local spec = obj.data.spec local spec = obj.data.spec
local podLabelKey = obj.revisionLabelKey
if spec.subsets == nil then
spec.subsets = {}
end
-- selector lables might come from pod-template-hash and patchPodTemplateMetadata
-- now we only support pod-template-hash
local stableLabels = {}
if obj.stableRevision ~= nil and obj.stableRevision ~= "" then
stableLabels[podLabelKey] = obj.stableRevision
end
local canaryLabels = {}
if obj.canaryRevision ~= nil and obj.canaryRevision ~= "" then
canaryLabels[podLabelKey] = obj.canaryRevision
end
local StableNameAlreadyExist = false
-- if stableName already exists, just appened the lables
for _, subset in ipairs(spec.subsets) do
if subset.name == obj.stableName then
StableNameAlreadyExist = true
if next(stableLabels) ~= nil then
subset.labels = subset.labels or {}
for key, value in pairs(stableLabels) do
subset.labels[key] = value
end
end
end
end
-- if stableName doesn't exist, create it and its labels
if not StableNameAlreadyExist then
local stable = {}
stable.name = obj.stableName
if next(stableLabels) ~= nil then
stable.labels = stableLabels
end
table.insert(spec.subsets, stable)
end
-- Aussue the canaryName never exist, create it and its labels
local canary = {} local canary = {}
canary.labels = {} canary.name = obj.canaryName
canary.name = "canary" if next(canaryLabels) ~= nil then
local podLabelKey = "istio.service.tag" canary.labels = canaryLabels
canary.labels[podLabelKey] = "gray" end
table.insert(spec.subsets, canary) table.insert(spec.subsets, canary)
return obj.data return obj.data

View File

@ -19,6 +19,9 @@ trafficRouting:
- apiVersion: networking.istio.io/v1alpha3 - apiVersion: networking.istio.io/v1alpha3
kind: VirtualService kind: VirtualService
name: vs-demo name: vs-demo
additionalParams:
istio.destinationRule.stableSubsetName: "base"
istio.destinationRule.canarySubsetName: "canary"
original: original:
apiVersion: networking.istio.io/v1alpha3 apiVersion: networking.istio.io/v1alpha3
kind: VirtualService kind: VirtualService

View File

@ -20,6 +20,9 @@ trafficRouting:
- apiVersion: networking.istio.io/v1alpha3 - apiVersion: networking.istio.io/v1alpha3
kind: VirtualService kind: VirtualService
name: vs-demo name: vs-demo
additionalParams:
istio.destinationRule.stableSubsetName: "base"
istio.destinationRule.canarySubsetName: "canary"
original: original:
apiVersion: networking.istio.io/v1alpha3 apiVersion: networking.istio.io/v1alpha3
kind: VirtualService kind: VirtualService

View File

@ -12,6 +12,9 @@ trafficRouting:
- apiVersion: networking.istio.io/v1alpha3 - apiVersion: networking.istio.io/v1alpha3
kind: VirtualService kind: VirtualService
name: vs-demo name: vs-demo
additionalParams:
istio.destinationRule.stableSubsetName: "base"
istio.destinationRule.canarySubsetName: "canary"
original: original:
apiVersion: networking.istio.io/v1alpha3 apiVersion: networking.istio.io/v1alpha3
kind: VirtualService kind: VirtualService

View File

@ -75,7 +75,7 @@ function GenerateRoutesWithMatches(spec, matches, stableService, canaryService)
-- stableService == canaryService indicates DestinationRule exists and subset is set to be canary by default -- stableService == canaryService indicates DestinationRule exists and subset is set to be canary by default
if stableService == canaryService then if stableService == canaryService then
route.route[1].destination.host = stableService route.route[1].destination.host = stableService
route.route[1].destination.subset = "canary" route.route[1].destination.subset = obj.canaryName
else else
route.route[1].destination.host = canaryService route.route[1].destination.host = canaryService
end end
@ -99,7 +99,7 @@ function GenerateRoutes(spec, stableService, canaryService, stableWeight, canary
canary = { canary = {
destination = { destination = {
host = stableService, host = stableService,
subset = "canary", subset = obj.canaryName,
}, },
weight = canaryWeight, weight = canaryWeight,
} }

View File

@ -250,14 +250,14 @@ func (m *canaryReleaseManager) doCanaryFinalising(c *RolloutContext) (bool, erro
if err != nil || !done { if err != nil || !done {
return done, err return done, err
} }
// 3. set workload.pause=false; set workload.partition=0 // 3. modify network api(ingress or gateway api) configuration, and route 100% traffic to stable pods.
done, err = m.finalizingBatchRelease(c) done, err = m.trafficRoutingManager.FinalisingTrafficRouting(tr, false)
c.NewStatus.CanaryStatus.LastUpdateTime = tr.LastUpdateTime
if err != nil || !done { if err != nil || !done {
return done, err return done, err
} }
// 4. modify network api(ingress or gateway api) configuration, and route 100% traffic to stable pods. // 4. set workload.pause=false; set workload.partition=0
done, err = m.trafficRoutingManager.FinalisingTrafficRouting(tr, false) done, err = m.finalizingBatchRelease(c)
c.NewStatus.CanaryStatus.LastUpdateTime = tr.LastUpdateTime
if err != nil || !done { if err != nil || !done {
return done, err return done, err
} }

View File

@ -278,14 +278,16 @@ func newNetworkProvider(c client.Client, con *TrafficRoutingContext, sService, c
trafficRouting := con.ObjectRef[0] trafficRouting := con.ObjectRef[0]
if trafficRouting.CustomNetworkRefs != nil { if trafficRouting.CustomNetworkRefs != nil {
return custom.NewCustomController(c, custom.Config{ return custom.NewCustomController(c, custom.Config{
Key: con.Key, Key: con.Key,
RolloutNs: con.Namespace, RolloutNs: con.Namespace,
CanaryService: cService, CanaryService: cService,
StableService: sService, StableService: sService,
TrafficConf: trafficRouting.CustomNetworkRefs, TrafficConf: trafficRouting.CustomNetworkRefs,
OwnerRef: con.OwnerRef, OwnerRef: con.OwnerRef,
//only set for CustomController, never work for Ingress and Gateway AdditionalParams: trafficRouting.AdditionalParams,
DisableGenerateCanaryService: con.DisableGenerateCanaryService, RevisionLabelKey: con.RevisionLabelKey,
StableRevision: con.StableRevision,
CanaryRevision: con.CanaryRevision,
}) })
} }
if trafficRouting.Ingress != nil { if trafficRouting.Ingress != nil {

View File

@ -319,12 +319,12 @@ var (
Data: map[string]string{ Data: map[string]string{
"lua.traffic.routing.VirtualService.networking.istio.io": ` "lua.traffic.routing.VirtualService.networking.istio.io": `
spec = obj.data.spec spec = obj.data.spec
if obj.canaryWeight == -1 then if obj.canaryWeight == -1 then
obj.canaryWeight = 100 obj.canaryWeight = 100
obj.stableWeight = 0 obj.stableWeight = 0
end end
function GetHost(destination) function GetHost(destination)
local host = destination.destination.host local host = destination.destination.host
dot_position = string.find(host, ".", 1, true) dot_position = string.find(host, ".", 1, true)
@ -333,7 +333,7 @@ var (
end end
return host return host
end end
-- find routes of VirtualService with stableService -- find routes of VirtualService with stableService
function GetRulesToPatch(spec, stableService, protocol) function GetRulesToPatch(spec, stableService, protocol)
local matchedRoutes = {} local matchedRoutes = {}
@ -351,7 +351,7 @@ var (
end end
return matchedRoutes return matchedRoutes
end end
function CalculateWeight(route, stableWeight, n) function CalculateWeight(route, stableWeight, n)
local weight local weight
if (route.weight) then if (route.weight) then
@ -361,7 +361,7 @@ var (
end end
return weight return weight
end end
-- generate routes with matches, insert a rule before other rules, only support http headers, cookies etc. -- 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)
for _, match in ipairs(matches) do for _, match in ipairs(matches) do
@ -395,14 +395,14 @@ var (
-- stableService == canaryService indicates DestinationRule exists and subset is set to be canary by default -- stableService == canaryService indicates DestinationRule exists and subset is set to be canary by default
if stableService == canaryService then if stableService == canaryService then
route.route[1].destination.host = stableService route.route[1].destination.host = stableService
route.route[1].destination.subset = "canary" route.route[1].destination.subset = obj.canaryName
else else
route.route[1].destination.host = canaryService route.route[1].destination.host = canaryService
end end
table.insert(spec.http, 1, route) table.insert(spec.http, 1, route)
end end
end end
-- generate routes without matches, change every rule whose host is stableService -- generate routes without matches, change every rule whose host is stableService
function GenerateRoutes(spec, stableService, canaryService, stableWeight, canaryWeight, protocol) function GenerateRoutes(spec, stableService, canaryService, stableWeight, canaryWeight, protocol)
local matchedRules = GetRulesToPatch(spec, stableService, protocol) local matchedRules = GetRulesToPatch(spec, stableService, protocol)
@ -419,12 +419,12 @@ var (
canary = { canary = {
destination = { destination = {
host = stableService, host = stableService,
subset = "canary", subset = obj.canaryName,
}, },
weight = canaryWeight, weight = canaryWeight,
} }
end end
-- incase there are multiple versions traffic already, do a for-loop -- incase there are multiple versions traffic already, do a for-loop
for _, route in ipairs(rule.route) do for _, route in ipairs(rule.route) do
-- update stable service weight -- update stable service weight
@ -433,7 +433,7 @@ var (
table.insert(rule.route, canary) table.insert(rule.route, canary)
end end
end end
if (obj.matches and next(obj.matches) ~= nil) if (obj.matches and next(obj.matches) ~= nil)
then then
GenerateRoutesWithMatches(spec, obj.matches, obj.stableService, obj.canaryService) GenerateRoutesWithMatches(spec, obj.matches, obj.stableService, obj.canaryService)
@ -443,22 +443,66 @@ var (
GenerateRoutes(spec, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight, "tls") GenerateRoutes(spec, obj.stableService, obj.canaryService, obj.stableWeight, obj.canaryWeight, "tls")
end end
return obj.data return obj.data
`, `,
"lua.traffic.routing.DestinationRule.networking.istio.io": ` "lua.traffic.routing.DestinationRule.networking.istio.io": `
local spec = obj.data.spec local spec = obj.data.spec
local podLabelKey = obj.revisionLabelKey
if spec.subsets == nil then
spec.subsets = {}
end
-- selector lables might come from pod-template-hash and patchPodTemplateMetadata
-- now we only support pod-template-hash
local stableLabels = {}
if obj.stableRevision ~= nil and obj.stableRevision ~= "" then
stableLabels[podLabelKey] = obj.stableRevision
end
local canaryLabels = {}
if obj.canaryRevision ~= nil and obj.canaryRevision ~= "" then
canaryLabels[podLabelKey] = obj.canaryRevision
end
local StableNameAlreadyExist = false
-- if stableName already exists, just appened the lables
for _, subset in ipairs(spec.subsets) do
if subset.name == obj.stableName then
StableNameAlreadyExist = true
if next(stableLabels) ~= nil then
subset.labels = subset.labels or {}
for key, value in pairs(stableLabels) do
subset.labels[key] = value
end
end
end
end
-- if stableName doesn't exist, create it and its labels
if not StableNameAlreadyExist then
local stable = {}
stable.name = obj.stableName
if next(stableLabels) ~= nil then
stable.labels = stableLabels
end
table.insert(spec.subsets, stable)
end
-- Aussue the canaryName never exist, create it and its labels
local canary = {} local canary = {}
canary.labels = {} canary.name = obj.canaryName
canary.name = "canary" if next(canaryLabels) ~= nil then
local podLabelKey = "istio.service.tag" canary.labels = canaryLabels
canary.labels[podLabelKey] = "gray" end
table.insert(spec.subsets, canary) table.insert(spec.subsets, canary)
return obj.data return obj.data
`, `,
}, },
} }
virtualServiceDemo = ` virtualServiceDemo1 = `
{ {
"apiVersion": "networking.istio.io/v1alpha3", "apiVersion": "networking.istio.io/v1alpha3",
"kind": "VirtualService", "kind": "VirtualService",
@ -486,7 +530,54 @@ var (
} }
} }
` `
destinationRuleDemo = ` destinationRuleDemo1 = `
{
"apiVersion": "networking.istio.io/v1alpha3",
"kind": "DestinationRule",
"metadata": {
"name": "dr-demo"
},
"spec": {
"host": "echoserver",
"trafficPolicy": {
"loadBalancer": {
"simple": "ROUND_ROBIN"
}
}
}
}
`
virtualServiceDemo2 = `
{
"apiVersion": "networking.istio.io/v1alpha3",
"kind": "VirtualService",
"metadata": {
"name": "echoserver",
"annotations": {
"virtual": "test"
}
},
"spec": {
"hosts": [
"echoserver.example.com"
],
"http": [
{
"route": [
{
"destination": {
"host": "echoserver",
"subset": "hello"
}
}
]
}
]
}
}
`
destinationRuleDemo2 = `
{ {
"apiVersion": "networking.istio.io/v1alpha3", "apiVersion": "networking.istio.io/v1alpha3",
"kind": "DestinationRule", "kind": "DestinationRule",
@ -497,10 +588,7 @@ var (
"host": "echoserver", "host": "echoserver",
"subsets": [ "subsets": [
{ {
"labels": { "name":"hello"
"version": "base"
},
"name": "version-base"
} }
], ],
"trafficPolicy": { "trafficPolicy": {
@ -757,12 +845,12 @@ func TestDoTrafficRoutingWithIstio(t *testing.T) {
getUnstructureds: func() []*unstructured.Unstructured { getUnstructureds: func() []*unstructured.Unstructured {
objects := make([]*unstructured.Unstructured, 0) objects := make([]*unstructured.Unstructured, 0)
u := &unstructured.Unstructured{} u := &unstructured.Unstructured{}
_ = u.UnmarshalJSON([]byte(virtualServiceDemo)) _ = u.UnmarshalJSON([]byte(virtualServiceDemo1))
u.SetAPIVersion("networking.istio.io/v1alpha3") u.SetAPIVersion("networking.istio.io/v1alpha3")
objects = append(objects, u) objects = append(objects, u)
u = &unstructured.Unstructured{} u = &unstructured.Unstructured{}
_ = u.UnmarshalJSON([]byte(destinationRuleDemo)) _ = u.UnmarshalJSON([]byte(destinationRuleDemo1))
u.SetAPIVersion("networking.istio.io/v1alpha3") u.SetAPIVersion("networking.istio.io/v1alpha3")
objects = append(objects, u) objects = append(objects, u)
return objects return objects
@ -770,12 +858,17 @@ func TestDoTrafficRoutingWithIstio(t *testing.T) {
getRollout: func() (*v1beta1.Rollout, *util.Workload) { getRollout: func() (*v1beta1.Rollout, *util.Workload) {
obj := demoIstioRollout.DeepCopy() obj := demoIstioRollout.DeepCopy()
obj.Status.CanaryStatus.LastUpdateTime = &metav1.Time{Time: time.Now().Add(-10 * time.Second)} obj.Status.CanaryStatus.LastUpdateTime = &metav1.Time{Time: time.Now().Add(-10 * time.Second)}
obj.Spec.Strategy.Canary.TrafficRoutings[0].AdditionalParams = map[string]string{
// map is empty, thus the subset names will be default value: stable and canary
// v1beta1.IstioStableSubsetName: "version-base",
// v1beta1.IstioCanarySubsetName: "canary",
}
return obj, &util.Workload{RevisionLabelKey: apps.DefaultDeploymentUniqueLabelKey} return obj, &util.Workload{RevisionLabelKey: apps.DefaultDeploymentUniqueLabelKey}
}, },
expectUnstructureds: func() []*unstructured.Unstructured { expectUnstructureds: func() []*unstructured.Unstructured {
objects := make([]*unstructured.Unstructured, 0) objects := make([]*unstructured.Unstructured, 0)
u := &unstructured.Unstructured{} u := &unstructured.Unstructured{}
_ = u.UnmarshalJSON([]byte(virtualServiceDemo)) _ = u.UnmarshalJSON([]byte(virtualServiceDemo1))
annotations := map[string]string{ annotations := map[string]string{
OriginalSpecAnnotation: `{"spec":{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver"}}]}]},"annotations":{"virtual":"test"}}`, OriginalSpecAnnotation: `{"spec":{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver"}}]}]},"annotations":{"virtual":"test"}}`,
"virtual": "test", "virtual": "test",
@ -788,12 +881,12 @@ func TestDoTrafficRoutingWithIstio(t *testing.T) {
objects = append(objects, u) objects = append(objects, u)
u = &unstructured.Unstructured{} u = &unstructured.Unstructured{}
_ = u.UnmarshalJSON([]byte(destinationRuleDemo)) _ = u.UnmarshalJSON([]byte(destinationRuleDemo1))
annotations = map[string]string{ annotations = map[string]string{
OriginalSpecAnnotation: `{"spec":{"host":"echoserver","subsets":[{"labels":{"version":"base"},"name":"version-base"}],"trafficPolicy":{"loadBalancer":{"simple":"ROUND_ROBIN"}}}}`, OriginalSpecAnnotation: `{"spec":{"host":"echoserver","trafficPolicy":{"loadBalancer":{"simple":"ROUND_ROBIN"}}}}`,
} }
u.SetAnnotations(annotations) u.SetAnnotations(annotations)
specStr = `{"host":"echoserver","subsets":[{"labels":{"version":"base"},"name":"version-base"},{"labels":{"istio.service.tag":"gray"},"name":"canary"}],"trafficPolicy":{"loadBalancer":{"simple":"ROUND_ROBIN"}}}` specStr = `{"host":"echoserver","subsets":[{"labels":{"pod-template-hash":"podtemplatehash-v1"},"name":"stable"},{"labels":{"pod-template-hash":"podtemplatehash-v2"},"name":"canary"}],"trafficPolicy":{"loadBalancer":{"simple":"ROUND_ROBIN"}}}`
_ = json.Unmarshal([]byte(specStr), &spec) _ = json.Unmarshal([]byte(specStr), &spec)
u.Object["spec"] = spec u.Object["spec"] = spec
objects = append(objects, u) objects = append(objects, u)
@ -815,12 +908,12 @@ func TestDoTrafficRoutingWithIstio(t *testing.T) {
getUnstructureds: func() []*unstructured.Unstructured { getUnstructureds: func() []*unstructured.Unstructured {
objects := make([]*unstructured.Unstructured, 0) objects := make([]*unstructured.Unstructured, 0)
u := &unstructured.Unstructured{} u := &unstructured.Unstructured{}
_ = u.UnmarshalJSON([]byte(virtualServiceDemo)) _ = u.UnmarshalJSON([]byte(virtualServiceDemo2))
u.SetAPIVersion("networking.istio.io/v1alpha3") u.SetAPIVersion("networking.istio.io/v1alpha3")
objects = append(objects, u) objects = append(objects, u)
u = &unstructured.Unstructured{} u = &unstructured.Unstructured{}
_ = u.UnmarshalJSON([]byte(destinationRuleDemo)) _ = u.UnmarshalJSON([]byte(destinationRuleDemo2))
u.SetAPIVersion("networking.istio.io/v1alpha3") u.SetAPIVersion("networking.istio.io/v1alpha3")
objects = append(objects, u) objects = append(objects, u)
return objects return objects
@ -830,30 +923,34 @@ func TestDoTrafficRoutingWithIstio(t *testing.T) {
// set DisableGenerateCanaryService as true // set DisableGenerateCanaryService as true
obj.Spec.Strategy.Canary.DisableGenerateCanaryService = true obj.Spec.Strategy.Canary.DisableGenerateCanaryService = true
obj.Status.CanaryStatus.LastUpdateTime = &metav1.Time{Time: time.Now().Add(-10 * time.Second)} obj.Status.CanaryStatus.LastUpdateTime = &metav1.Time{Time: time.Now().Add(-10 * time.Second)}
obj.Spec.Strategy.Canary.TrafficRoutings[0].AdditionalParams = map[string]string{
v1beta1.IstioStableSubsetName: "hello",
v1beta1.IstioCanarySubsetName: "world",
}
return obj, &util.Workload{RevisionLabelKey: apps.DefaultDeploymentUniqueLabelKey} return obj, &util.Workload{RevisionLabelKey: apps.DefaultDeploymentUniqueLabelKey}
}, },
expectUnstructureds: func() []*unstructured.Unstructured { expectUnstructureds: func() []*unstructured.Unstructured {
objects := make([]*unstructured.Unstructured, 0) objects := make([]*unstructured.Unstructured, 0)
u := &unstructured.Unstructured{} u := &unstructured.Unstructured{}
_ = u.UnmarshalJSON([]byte(virtualServiceDemo)) _ = u.UnmarshalJSON([]byte(virtualServiceDemo2))
annotations := map[string]string{ annotations := map[string]string{
OriginalSpecAnnotation: `{"spec":{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver"}}]}]},"annotations":{"virtual":"test"}}`, OriginalSpecAnnotation: `{"spec":{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver","subset":"hello"}}]}]},"annotations":{"virtual":"test"}}`,
"virtual": "test", "virtual": "test",
} }
u.SetAnnotations(annotations) u.SetAnnotations(annotations)
specStr := `{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver"},"weight":95},{"destination":{"host":"echoserver","subset":"canary"},"weight":5}]}]}` specStr := `{"hosts":["echoserver.example.com"],"http":[{"route":[{"destination":{"host":"echoserver","subset":"hello"},"weight":95},{"destination":{"host":"echoserver","subset":"world"},"weight":5}]}]}`
var spec interface{} var spec interface{}
_ = json.Unmarshal([]byte(specStr), &spec) _ = json.Unmarshal([]byte(specStr), &spec)
u.Object["spec"] = spec u.Object["spec"] = spec
objects = append(objects, u) objects = append(objects, u)
u = &unstructured.Unstructured{} u = &unstructured.Unstructured{}
_ = u.UnmarshalJSON([]byte(destinationRuleDemo)) _ = u.UnmarshalJSON([]byte(destinationRuleDemo2))
annotations = map[string]string{ annotations = map[string]string{
OriginalSpecAnnotation: `{"spec":{"host":"echoserver","subsets":[{"labels":{"version":"base"},"name":"version-base"}],"trafficPolicy":{"loadBalancer":{"simple":"ROUND_ROBIN"}}}}`, OriginalSpecAnnotation: `{"spec":{"host":"echoserver","subsets":[{"name":"hello"}],"trafficPolicy":{"loadBalancer":{"simple":"ROUND_ROBIN"}}}}`,
} }
u.SetAnnotations(annotations) u.SetAnnotations(annotations)
specStr = `{"host":"echoserver","subsets":[{"labels":{"version":"base"},"name":"version-base"},{"labels":{"istio.service.tag":"gray"},"name":"canary"}],"trafficPolicy":{"loadBalancer":{"simple":"ROUND_ROBIN"}}}` specStr = `{"host":"echoserver","subsets":[{"labels":{"pod-template-hash":"podtemplatehash-v1"},"name":"hello"},{"labels":{"pod-template-hash":"podtemplatehash-v2"},"name":"world"}],"trafficPolicy":{"loadBalancer":{"simple":"ROUND_ROBIN"}}}`
_ = json.Unmarshal([]byte(specStr), &spec) _ = json.Unmarshal([]byte(specStr), &spec)
u.Object["spec"] = spec u.Object["spec"] = spec
objects = append(objects, u) objects = append(objects, u)

View File

@ -53,6 +53,16 @@ type LuaData struct {
Matches []v1beta1.HttpRouteMatch Matches []v1beta1.HttpRouteMatch
CanaryService string CanaryService string
StableService string StableService string
// workload.RevisionLabelKey
RevisionLabelKey string
// status.CanaryStatus.StableRevision
StableRevision string
// status.CanaryStatus.PodTemplateHash
CanaryRevision string
// specify the name of subset used as stable subset, default 'stable'; if not found, create a subset named stable
StableName string
// sepcify the name of canary subset that will be created, default 'canary'
CanaryName string
} }
type Data struct { type Data struct {
Spec interface{} `json:"spec,omitempty"` Spec interface{} `json:"spec,omitempty"`
@ -72,9 +82,16 @@ type Config struct {
CanaryService string CanaryService string
StableService string StableService string
// network providers need to be created // network providers need to be created
TrafficConf []v1beta1.ObjectRef TrafficConf []v1beta1.ObjectRef
OwnerRef metav1.OwnerReference OwnerRef metav1.OwnerReference
DisableGenerateCanaryService bool // DisableGenerateCanaryService bool
AdditionalParams map[string]string
// workload.RevisionLabelKey
RevisionLabelKey string
// status.CanaryStatus.StableRevision
StableRevision string
// status.CanaryStatus.PodTemplateHash
CanaryRevision string
} }
func NewCustomController(client client.Client, conf Config) (network.NetworkProvider, error) { func NewCustomController(client client.Client, conf Config) (network.NetworkProvider, error) {
@ -268,13 +285,27 @@ func (r *customController) executeLuaForCanary(spec Data, strategy *v1beta1.Traf
// so we need to pass weight=-1 to indicate the case where weight is nil. // so we need to pass weight=-1 to indicate the case where weight is nil.
weight = utilpointer.Int32(-1) weight = utilpointer.Int32(-1)
} }
// default value
stableSubsetName, ok := r.conf.AdditionalParams[v1beta1.IstioStableSubsetName]
if !ok {
stableSubsetName = "stable" //default value
}
canarySubsetName, ok := r.conf.AdditionalParams[v1beta1.IstioCanarySubsetName]
if !ok {
canarySubsetName = "canary" //default value
}
data := &LuaData{ data := &LuaData{
Data: spec, Data: spec,
CanaryWeight: *weight, CanaryWeight: *weight,
StableWeight: 100 - *weight, StableWeight: 100 - *weight,
Matches: matches, Matches: matches,
CanaryService: r.conf.CanaryService, CanaryService: r.conf.CanaryService,
StableService: r.conf.StableService, StableService: r.conf.StableService,
RevisionLabelKey: r.conf.RevisionLabelKey,
StableRevision: r.conf.StableRevision,
CanaryRevision: r.conf.CanaryRevision,
StableName: stableSubsetName,
CanaryName: canarySubsetName,
} }
unObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(data) unObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(data)

View File

@ -89,9 +89,6 @@ var (
"host": "mockb", "host": "mockb",
"subsets": [ "subsets": [
{ {
"labels": {
"version": "base"
},
"name": "version-base" "name": "version-base"
} }
], ],
@ -281,6 +278,13 @@ func TestEnsureRoutes(t *testing.T) {
Name: "dr-demo", Name: "dr-demo",
}, },
}, },
AdditionalParams: map[string]string{
v1beta1.IstioStableSubsetName: "version-base",
v1beta1.IstioCanarySubsetName: "canary",
},
StableRevision: "podtemplatehash-v1",
CanaryRevision: "podtemplatehash-v2",
RevisionLabelKey: "pod-template-hash",
} }
}, },
expectUnstructureds: func() []*unstructured.Unstructured { expectUnstructureds: func() []*unstructured.Unstructured {
@ -301,10 +305,10 @@ func TestEnsureRoutes(t *testing.T) {
u = &unstructured.Unstructured{} u = &unstructured.Unstructured{}
_ = u.UnmarshalJSON([]byte(destinationRuleDemo)) _ = u.UnmarshalJSON([]byte(destinationRuleDemo))
annotations = map[string]string{ annotations = map[string]string{
OriginalSpecAnnotation: `{"spec":{"host":"mockb","subsets":[{"labels":{"version":"base"},"name":"version-base"}],"trafficPolicy":{"loadBalancer":{"simple":"ROUND_ROBIN"}}}}`, OriginalSpecAnnotation: `{"spec":{"host":"mockb","subsets":[{"name":"version-base"}],"trafficPolicy":{"loadBalancer":{"simple":"ROUND_ROBIN"}}}}`,
} }
u.SetAnnotations(annotations) u.SetAnnotations(annotations)
specStr = `{"host":"mockb","subsets":[{"labels":{"version":"base"},"name":"version-base"},{"labels":{"istio.service.tag":"gray"},"name":"canary"}],"trafficPolicy":{"loadBalancer":{"simple":"ROUND_ROBIN"}}}` specStr = `{"host":"mockb","subsets":[{"labels":{"pod-template-hash":"podtemplatehash-v1"},"name":"version-base"},{"labels":{"pod-template-hash":"podtemplatehash-v2"},"name":"canary"}],"trafficPolicy":{"loadBalancer":{"simple":"ROUND_ROBIN"}}}`
_ = json.Unmarshal([]byte(specStr), &spec) _ = json.Unmarshal([]byte(specStr), &spec)
u.Object["spec"] = spec u.Object["spec"] = spec
objects = append(objects, u) objects = append(objects, u)
@ -540,11 +544,16 @@ func TestLuaScript(t *testing.T) {
Annotations: testCase.Original.GetAnnotations(), Annotations: testCase.Original.GetAnnotations(),
Spec: testCase.Original.Object["spec"], Spec: testCase.Original.Object["spec"],
}, },
Matches: step.TrafficRoutingStrategy.Matches, Matches: step.TrafficRoutingStrategy.Matches,
CanaryWeight: *weight, CanaryWeight: *weight,
StableWeight: 100 - *weight, StableWeight: 100 - *weight,
CanaryService: canaryService, CanaryService: canaryService,
StableService: stableService, StableService: stableService,
StableRevision: "podtemplatehash-v1",
CanaryRevision: "podtemplatehash-v2",
RevisionLabelKey: "pod-template-hash",
StableName: rollout.Spec.Strategy.Canary.TrafficRoutings[0].AdditionalParams[v1beta1.IstioStableSubsetName],
CanaryName: rollout.Spec.Strategy.Canary.TrafficRoutings[0].AdditionalParams[v1beta1.IstioCanarySubsetName],
} }
nSpec, err := executeLua(data, script) nSpec, err := executeLua(data, script)
if err != nil { if err != nil {
@ -580,11 +589,16 @@ func TestLuaScript(t *testing.T) {
Annotations: testCase.Original.GetAnnotations(), Annotations: testCase.Original.GetAnnotations(),
Spec: testCase.Original.Object["spec"], Spec: testCase.Original.Object["spec"],
}, },
Matches: matches, Matches: matches,
CanaryWeight: *weight, CanaryWeight: *weight,
StableWeight: 100 - *weight, StableWeight: 100 - *weight,
CanaryService: canaryService, CanaryService: canaryService,
StableService: stableService, StableService: stableService,
StableRevision: "podtemplatehash-v1",
CanaryRevision: "podtemplatehash-v2",
RevisionLabelKey: "pod-template-hash",
StableName: trafficRouting.Spec.ObjectRef[0].AdditionalParams[v1beta1.IstioStableSubsetName],
CanaryName: trafficRouting.Spec.ObjectRef[0].AdditionalParams[v1beta1.IstioCanarySubsetName],
} }
nSpec, err := executeLua(data, script) nSpec, err := executeLua(data, script)
if err != nil { if err != nil {

View File

@ -1,8 +1,51 @@
local spec = obj.data.spec local spec = obj.data.spec
local podLabelKey = obj.revisionLabelKey
if spec.subsets == nil then
spec.subsets = {}
end
-- selector lables might come from pod-template-hash and patchPodTemplateMetadata
-- now we only support pod-template-hash
local stableLabels = {}
if obj.stableRevision ~= nil and obj.stableRevision ~= "" then
stableLabels[podLabelKey] = obj.stableRevision
end
local canaryLabels = {}
if obj.canaryRevision ~= nil and obj.canaryRevision ~= "" then
canaryLabels[podLabelKey] = obj.canaryRevision
end
local StableNameAlreadyExist = false
-- if stableName already exists, just appened the lables
for _, subset in ipairs(spec.subsets) do
if subset.name == obj.stableName then
StableNameAlreadyExist = true
if next(stableLabels) ~= nil then
subset.labels = subset.labels or {}
for key, value in pairs(stableLabels) do
subset.labels[key] = value
end
end
end
end
-- if stableName doesn't exist, create it and its labels
if not StableNameAlreadyExist then
local stable = {}
stable.name = obj.stableName
if next(stableLabels) ~= nil then
stable.labels = stableLabels
end
table.insert(spec.subsets, stable)
end
-- Aussue the canaryName never exist, create it and its labels
local canary = {} local canary = {}
canary.labels = {} canary.name = obj.canaryName
canary.name = "canary" if next(canaryLabels) ~= nil then
local podLabelKey = "istio.service.tag" canary.labels = canaryLabels
canary.labels[podLabelKey] = "gray" end
table.insert(spec.subsets, canary) table.insert(spec.subsets, canary)
return obj.data return obj.data

View File

@ -159,7 +159,7 @@ function GenerateMatchedRoutes(spec, matches, stableService, canaryService, stab
-- if stableService == canaryService, then do e2e release -- if stableService == canaryService, then do e2e release
if stableService == canaryService then if stableService == canaryService then
route.route[1].destination.host = stableService route.route[1].destination.host = stableService
route.route[1].destination.subset = "canary" route.route[1].destination.subset = obj.canaryName
else else
route.route[1].destination.host = canaryService route.route[1].destination.host = canaryService
end end
@ -189,7 +189,7 @@ function GenerateRoutes(spec, stableService, canaryService, stableWeight, canary
canary = { canary = {
destination = { destination = {
host = stableService, host = stableService,
subset = "canary", subset = obj.canaryName,
}, },
weight = canaryWeight, weight = canaryWeight,
} }