Skip to content

Commit

Permalink
Support canary services in multiple routes of the same proxy (#76)
Browse files Browse the repository at this point in the history

Signed-off-by: Frank Hamand <frankhamand@gmail.com>
  • Loading branch information
frankh committed Apr 16, 2024
1 parent 5844d9d commit 4462886
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 35 deletions.
82 changes: 48 additions & 34 deletions pkg/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,16 @@ func createPatch(httpProxy *contourv1.HTTPProxy, rollout *v1alpha1.Rollout, cana
return nil, "", fmt.Errorf("failed to marshal the current configuration: %w", err)
}

canarySvc, stableSvc, totalWeight, err := getRouteServices(httpProxy, rollout)
canarySvcs, stableSvcs, totalWeights, err := getRouteServices(httpProxy, rollout)
if err != nil {
return nil, types.MergePatchType, err
}
slog.Debug("old weight", slog.Int64("canary", canarySvc.Weight), slog.Int64("stable", stableSvc.Weight))

canarySvc.Weight, stableSvc.Weight = utils.CalcWeight(totalWeight, float32(canaryWeightPercent))
slog.Debug("new weight", slog.Int64("canary", canarySvc.Weight), slog.Int64("stable", stableSvc.Weight))
for i := range canarySvcs {
slog.Debug("old weight", slog.Int64("canary", canarySvcs[i].Weight), slog.Int64("stable", stableSvcs[i].Weight))
canarySvcs[i].Weight, stableSvcs[i].Weight = utils.CalcWeight(totalWeights[i], float32(canaryWeightPercent))
slog.Debug("new weight", slog.Int64("canary", canarySvcs[i].Weight), slog.Int64("stable", stableSvcs[i].Weight))
}

newData, err := json.Marshal(httpProxy)
if err != nil {
Expand Down Expand Up @@ -221,53 +223,64 @@ func (r *RpcPlugin) verifyHTTPProxy(
return false, nil
}

canarySvc, stableSvc, totalWeight, err := getRouteServices(httpProxy, rollout)
canarySvcs, stableSvcs, totalWeights, err := getRouteServices(httpProxy, rollout)
if err != nil {
return false, err
}

canaryWeight, stableWeight := utils.CalcWeight(totalWeight, float32(canaryWeightPercent))
if canarySvc.Weight != canaryWeight || stableSvc.Weight != stableWeight {
slog.Debug(fmt.Sprintf("expected weights are canary=%d and stable=%d, but got canary=%d and stable=%d", canaryWeight, stableWeight, canarySvc.Weight, stableSvc.Weight), slog.String("name", httpProxyName))
return false, nil
for i := range canarySvcs {
canaryWeight, stableWeight := utils.CalcWeight(totalWeights[i], float32(canaryWeightPercent))
if canarySvcs[i].Weight != canaryWeight || stableSvcs[i].Weight != stableWeight {
slog.Debug(fmt.Sprintf("expected weights are canary=%d and stable=%d, but got canary=%d and stable=%d", canaryWeight, stableWeight, canarySvcs[i].Weight, stableSvcs[i].Weight), slog.String("name", httpProxyName))
return false, nil
}
}

return true, nil
}

func getRouteServices(httpProxy *contourv1.HTTPProxy, rollout *v1alpha1.Rollout) (
*contourv1.Service, *contourv1.Service, int64, error) {
[]*contourv1.Service, []*contourv1.Service, []int64, error) {
canarySvcName := rollout.Spec.Strategy.Canary.CanaryService
stableSvcName := rollout.Spec.Strategy.Canary.StableService

slog.Debug("the services name", slog.String("stable", stableSvcName), slog.String("canary", canarySvcName))

svcMap := getServiceMap(httpProxy, canarySvcName)
svcMaps := getRouteServiceMaps(httpProxy, canarySvcName)
canarySvcs := []*contourv1.Service{}
stableSvcs := []*contourv1.Service{}
totalWeights := []int64{}

canarySvc, err := getService(canarySvcName, svcMap)
if err != nil {
return nil, nil, 0, err
}
for _, svcMap := range svcMaps {
canarySvc, err := getService(canarySvcName, svcMap)
if err != nil {
return nil, nil, nil, err
}

stableSvc, err := getService(stableSvcName, svcMap)
if err != nil {
return nil, nil, 0, err
}
stableSvc, err := getService(stableSvcName, svcMap)
if err != nil {
return nil, nil, nil, err
}

otherWeight := int64(0)
for name, svc := range svcMap {
if name == stableSvcName || name == canarySvcName {
continue
otherWeight := int64(0)
for name, svc := range svcMap {
if name == stableSvcName || name == canarySvcName {
continue
}
otherWeight += svc.Weight
}
otherWeight += svc.Weight
}

// the total weight must equals to 100
if otherWeight+canarySvc.Weight+stableSvc.Weight != 100 {
return nil, nil, 0, fmt.Errorf("the total weight must equals to 100")
// the total weight must equals to 100
if otherWeight+canarySvc.Weight+stableSvc.Weight != 100 {
return nil, nil, nil, fmt.Errorf("the total weight must equals to 100")
}

canarySvcs = append(canarySvcs, canarySvc)
stableSvcs = append(stableSvcs, stableSvc)
totalWeights = append(totalWeights, 100-otherWeight)
}

return canarySvc, stableSvc, 100 - otherWeight, nil
return canarySvcs, stableSvcs, totalWeights, nil
}

func getContourTrafficRouting(rollout *v1alpha1.Rollout) (*ContourTrafficRouting, error) {
Expand All @@ -286,9 +299,8 @@ func getService(name string, svcMap map[string]*contourv1.Service) (*contourv1.S
return svc, nil
}

func getServiceMap(httpProxy *contourv1.HTTPProxy, canarySvcName string) map[string]*contourv1.Service {
svcMap := make(map[string]*contourv1.Service)

func getRouteServiceMaps(httpProxy *contourv1.HTTPProxy, canarySvcName string) []map[string]*contourv1.Service {
svcMaps := []map[string]*contourv1.Service{}
// filter the services by canary service name
filter := func(services []contourv1.Service) bool {
for _, svc := range services {
Expand All @@ -298,17 +310,19 @@ func getServiceMap(httpProxy *contourv1.HTTPProxy, canarySvcName string) map[str
}
return false
}
// TODO: same service in multi conditions

for _, r := range httpProxy.Spec.Routes {
svcMap := make(map[string]*contourv1.Service)
if filter(r.Services) {
svcMaps = append(svcMaps, svcMap)
for i := range r.Services {
s := &r.Services[i]
svcMap[s.Name] = s
}
}

}
return svcMap
return svcMaps
}

func validateRolloutParameters(rollout *v1alpha1.Rollout) error {
Expand Down
62 changes: 61 additions & 1 deletion pkg/plugin/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,66 @@ func Test_createPatch(t *testing.T) {
wantPatchType: k8stypes.MergePatchType,
wantErr: false,
},
{
name: "test create http proxy patch",
args: args{
httpProxy: &contourv1.HTTPProxy{
ObjectMeta: metav1.ObjectMeta{
Name: mocks.HTTPProxyName,
},
Spec: contourv1.HTTPProxySpec{
Routes: []contourv1.Route{
{
Services: []contourv1.Service{
{
Name: mocks.StableServiceName,
Weight: 70,
},
{
Name: mocks.CanaryServiceName,
Weight: 20,
},
{
Name: "others-service",
Weight: 10,
},
},
},
{
Services: []contourv1.Service{
{
Name: mocks.StableServiceName,
Weight: 70,
},
{
Name: mocks.CanaryServiceName,
Weight: 10,
},
{
Name: "others-service",
Weight: 20,
},
},
},
},
},
},
rollout: &v1alpha1.Rollout{
Spec: v1alpha1.RolloutSpec{
Strategy: v1alpha1.RolloutStrategy{
Canary: &v1alpha1.CanaryStrategy{
StableService: mocks.StableServiceName,
CanaryService: mocks.CanaryServiceName,
},
},
},
},
desiredWeight: 50,
},
want: []byte(`{"spec":{"routes":[{"services":[{"name":"argo-rollouts-stable","port":0,"weight":45},{"name":"argo-rollouts-canary","port":0,"weight":45},{"name":"others-service","port":0,"weight":10}]},{"services":[{"name":"argo-rollouts-stable","port":0,"weight":40},{"name":"argo-rollouts-canary","port":0,"weight":40},{"name":"others-service","port":0,"weight":20}]}]}}`),
wantPatchType: k8stypes.MergePatchType,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -292,7 +352,7 @@ func Test_createPatch(t *testing.T) {
t.Errorf("createPatch() gotPatchType = %v, want %v", gotPatchType, tt.wantPatchType)
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("createPatch() got = %v, want %v", got, tt.want)
t.Errorf("createPatch() got = %s, want %s", got, tt.want)
}
})
}
Expand Down

0 comments on commit 4462886

Please sign in to comment.