// Copyright © 2019 The Knative Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package traffic import ( "github.com/knative/serving/pkg/apis/serving/v1alpha1" "gotest.tools/assert" "testing" "github.com/knative/client/pkg/kn/commands/flags" "github.com/spf13/cobra" ) type trafficTestCase struct { name string existingTraffic []v1alpha1.TrafficTarget inputFlags []string desiredRevisions []string desiredTags []string desiredPercents []int } type trafficErrorTestCase struct { name string existingTraffic []v1alpha1.TrafficTarget inputFlags []string errMsg string } func newTestTrafficCommand() (*cobra.Command, *flags.Traffic) { var trafficFlags flags.Traffic trafficCmd := &cobra.Command{ Use: "kn", Short: "Traffic test kn command", Run: func(cmd *cobra.Command, args []string) {}, } trafficFlags.Add(trafficCmd) return trafficCmd, &trafficFlags } func TestCompute(t *testing.T) { for _, testCase := range []trafficTestCase{ { "assign 'latest' tag to @latest revision", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("", "", 100, true)), []string{"--tag", "@latest=latest"}, []string{"@latest"}, []string{"latest"}, []int{100}, }, { "assign tag to revision", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("", "echo-v1", 100, false)), []string{"--tag", "echo-v1=stable"}, []string{"echo-v1"}, []string{"stable"}, []int{100}, }, { "re-assign same tag to same revision (unchanged)", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("current", "", 100, true)), []string{"--tag", "@latest=current"}, []string{""}, []string{"current"}, []int{100}, }, { "split traffic to tags", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("", "", 100, true), newTarget("", "rev-v1", 0, false)), []string{"--traffic", "@latest=10,rev-v1=90"}, []string{"@latest", "rev-v1"}, []string{"", ""}, []int{10, 90}, }, { "split traffic to tags with '%' suffix", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("", "", 100, true), newTarget("", "rev-v1", 0, false)), []string{"--traffic", "@latest=10%,rev-v1=90%"}, []string{"@latest", "rev-v1"}, []string{"", ""}, []int{10, 90}, }, { "add 2 more tagged revisions without giving them traffic portions", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("latest", "", 100, true)), []string{"--tag", "echo-v0=stale,echo-v1=old"}, []string{"@latest", "echo-v0", "echo-v1"}, []string{"latest", "stale", "old"}, []int{100, 0, 0}, }, { "re-assign same tag to 'echo-v1' revision", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("latest", "echo-v1", 100, false)), []string{"--tag", "echo-v1=latest"}, []string{"echo-v1"}, []string{"latest"}, []int{100}, }, { "set 2% traffic to latest revision by appending it in traffic block", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("latest", "echo-v1", 100, false)), []string{"--traffic", "@latest=2,echo-v1=98"}, []string{"echo-v1", "@latest"}, []string{"latest", ""}, []int{98, 2}, }, { "set 2% to @latest with tag (append it in traffic block)", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("latest", "echo-v1", 100, false)), []string{"--traffic", "@latest=2,echo-v1=98", "--tag", "@latest=testing"}, []string{"echo-v1", "@latest"}, []string{"latest", "testing"}, []int{98, 2}, }, { "change traffic percent of an existing revision in traffic block, add new revision with traffic share", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("v1", "echo-v1", 100, false)), []string{"--tag", "echo-v2=v2", "--traffic", "v1=10,v2=90"}, []string{"echo-v1", "echo-v2"}, []string{"v1", "v2"}, []int{10, 90}, //default value, }, { "untag 'latest' tag from 'echo-v1' revision", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("latest", "echo-v1", 100, false)), []string{"--untag", "latest"}, []string{"echo-v1"}, []string{""}, []int{100}, }, { "replace revision pointing to 'latest' tag from 'echo-v1' to 'echo-v2' revision", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("latest", "echo-v1", 50, false), newTarget("", "echo-v2", 50, false)), []string{"--untag", "latest", "--tag", "echo-v1=old,echo-v2=latest"}, []string{"echo-v1", "echo-v2"}, []string{"old", "latest"}, []int{50, 50}, }, { "have multiple tags for a revision, revision present in traffic block", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("latest", "echo-v1", 50, false), newTarget("", "echo-v2", 50, false)), []string{"--tag", "echo-v1=latest,echo-v1=current"}, []string{"echo-v1", "echo-v2", "echo-v1"}, // appends a new target []string{"latest", "", "current"}, // with new tag requested []int{50, 50, 0}, // and assign 0% to it }, { "have multiple tags for a revision, revision absent in traffic block", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("", "echo-v2", 100, false)), []string{"--tag", "echo-v1=latest,echo-v1=current"}, []string{"echo-v2", "echo-v1", "echo-v1"}, // appends two new targets []string{"", "latest", "current"}, // with new tags requested []int{100, 0, 0}, // and assign 0% to each }, { "re-assign same tag 'current' to @latest", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("current", "", 100, true)), []string{"--tag", "@latest=current"}, []string{""}, []string{"current"}, // since no change, no error []int{100}, }, { "assign echo-v1 10% traffic adjusting rest to @latest, echo-v1 isn't present in existing traffic block", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("", "", 100, true)), []string{"--traffic", "echo-v1=10,@latest=90"}, []string{"", "echo-v1"}, []string{"", ""}, // since no change, no error []int{90, 10}, }, } { t.Run(testCase.name, func(t *testing.T) { if lper, lrev, ltag := len(testCase.desiredPercents), len(testCase.desiredRevisions), len(testCase.desiredTags); lper != lrev || lper != ltag { t.Fatalf("length of desird revisions, tags and percents is mismatched: got=(desiredPercents, desiredRevisions, desiredTags)=(%d, %d, %d)", lper, lrev, ltag) } testCmd, tFlags := newTestTrafficCommand() testCmd.SetArgs(testCase.inputFlags) testCmd.Execute() targets, err := Compute(testCmd, testCase.existingTraffic, tFlags) if err != nil { t.Fatal(err) } for i, target := range targets { if testCase.desiredRevisions[i] == "@latest" { assert.Equal(t, *target.LatestRevision, true) } else { assert.Equal(t, target.RevisionName, testCase.desiredRevisions[i]) } assert.Equal(t, target.Tag, testCase.desiredTags[i]) assert.Equal(t, target.Percent, testCase.desiredPercents[i]) } }) } } func TestComputeErrMsg(t *testing.T) { for _, testCase := range []trafficErrorTestCase{ { "invalid format for --traffic option", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("", "", 100, true)), []string{"--traffic", "@latest=100=latest"}, "expecting the value format in value1=value2, given @latest=100=latest", }, { "invalid format for --tag option", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("", "", 100, true)), []string{"--tag", "@latest="}, "expecting the value format in value1=value2, given @latest=", }, { "repeatedly spliting traffic to @latest revision", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("", "", 100, true)), []string{"--traffic", "@latest=90,@latest=10"}, "repetition of identifier @latest is not allowed, use only once with --traffic flag", }, { "repeatedly tagging to @latest revision not allowed", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("", "", 100, true)), []string{"--tag", "@latest=latest,@latest=2"}, "repetition of identifier @latest is not allowed, use only once with --tag flag", }, { "overwriting tag not allowed, to @latest from other revision", append(append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("latest", "", 2, true)), newTarget("stable", "echo-v2", 98, false)), []string{"--tag", "@latest=stable"}, "refusing to overwrite existing tag in service, add flag '--untag stable' in command to untag it", }, { "overwriting tag not allowed, to a revision from other revision", append(append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("latest", "", 2, true)), newTarget("stable", "echo-v2", 98, false)), []string{"--tag", "echo-v2=latest"}, "refusing to overwrite existing tag in service, add flag '--untag latest' in command to untag it", }, { "overwriting tag of @latest not allowed, existing != requested", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("candidate", "", 100, true)), []string{"--tag", "@latest=current"}, "tag 'candidate' exists on latest ready revision of service, refusing to overwrite existing tag with 'current', add flag '--untag candidate' in command to untag it", }, { "verify error for non integer values given to percent", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("", "", 100, true)), []string{"--traffic", "@latest=100p"}, "error converting given 100p to integer value for traffic distribution", }, { "verify error for traffic sum not equal to 100", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("", "", 100, true)), []string{"--traffic", "@latest=19,echo-v1=71"}, "given traffic percents sum to 90, want 100", }, { "verify error for values out of range given to percent", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("", "", 100, true)), []string{"--traffic", "@latest=-100"}, "invalid value for traffic percent -100, expected 0 <= percent <= 100", }, { "repeatedly spliting traffic to the same revision", append(newServiceTraffic([]v1alpha1.TrafficTarget{}), newTarget("", "", 100, true)), []string{"--traffic", "echo-v1=40", "--traffic", "echo-v1=60"}, "repetition of revision reference echo-v1 is not allowed, use only once with --traffic flag", }, } { t.Run(testCase.name, func(t *testing.T) { testCmd, tFlags := newTestTrafficCommand() testCmd.SetArgs(testCase.inputFlags) testCmd.Execute() _, err := Compute(testCmd, testCase.existingTraffic, tFlags) assert.Error(t, err, testCase.errMsg) }) } }