mirror of https://github.com/knative/client.git
feature(service): Implements traffic splitting and tagging targets (#345)
- Add e2e tests
- Use '=' for traffic and tag assignment instead of ':'
- Use --tag and --untag flags for tagging traffic targets
- Use --traffic flag for setting traffic portions
- Allow --traffic portion to either take revisionName or tagName
- Uses @latest identifier for referencing latest revision of service
- Dont throw error if requested revision=tag pair is same
- Support having multiple tags for a revision
- creates a new target in traffic block if revision present in traffic block with new tag requested
- creates N new targets in traffic block if revision absent in traffic block with Nxnew tags requested
- Ensure updating tag of @latest requires --untag flag
- streamline updating tag for latestReadyRevision
- adds respective tests
- adds tests for ensuring given traffic sum to 100 on CLI and fail fast
- Add note about preference of order in case where tagOfOneRevision == revisionOfAnother,
first tags are checked and assigned traffic if any, as tags are supposed to be
unique in traffic block and should be referenced in such scenario.
- Remove the examples from flag description, moves it to service update command example section
- Pass only traffic block to compute trffic, makes it better to consume.
- Cover more error cases for invalid value format for assignments, covers a=b=c, a=, =b, or variants of them
- Separate and improves the error messages
- Add unit tests for traffic computing
- Add sanity checks in dedicated function verifyInputSanity
- traffic perents should sum to 100
- individual percent should be in 0-100
- repetition of @latest or tagName or revisionRef is disallowed
- Verify traffic percents sum to 100 on client side and fail fast
- Add e2e tests for traffic splitting
- create and update service, assign tags and set traffic to make an existing state
- run the scenario on existing state of service
- form the desired state traffic block
- extract the traffic block and form the traffic block struct actual state
- assert.DeepEqual actual and desired traffic blocks
- Use logic to generate service name in the same way as namespace, use different service name per test case
- Run e2e test for traffic splitting in parallel
- Use timeout duration of 30m for e2e tests, use timeout parameter for go_test_e2e library function
- Use tagName in flag description of --untag, avoiding conflict with --tag flag
- Update CHANGELOG
This commit is contained in:
parent
c4e8d5a964
commit
746dacc47c
|
|
@ -42,6 +42,11 @@
|
||||||
| Add --revision-name flag
|
| Add --revision-name flag
|
||||||
| https://github.com/knative/client/pull/282[#282]
|
| https://github.com/knative/client/pull/282[#282]
|
||||||
|
|
||||||
|
| 🎁
|
||||||
|
| Support traffic splitting and tagging targets
|
||||||
|
| https://github.com/knative/client/pull/345[#345]
|
||||||
|
|
||||||
|
|
||||||
|===
|
|===
|
||||||
|
|
||||||
## v0.2.0 (2019-07-10)
|
## v0.2.0 (2019-07-10)
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,25 @@ kn service update NAME [flags]
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Updates a service 'mysvc' with new environment variables
|
# Updates a service 'svc' with new environment variables
|
||||||
kn service update mysvc --env KEY1=VALUE1 --env KEY2=VALUE2
|
kn service update svc --env KEY1=VALUE1 --env KEY2=VALUE2
|
||||||
|
|
||||||
# Update a service 'mysvc' with new port
|
# Update a service 'svc' with new port
|
||||||
kn service update mysvc --port 80
|
kn service update svc --port 80
|
||||||
|
|
||||||
# Updates a service 'mysvc' with new requests and limits parameters
|
# Updates a service 'svc' with new requests and limits parameters
|
||||||
kn service update mysvc --requests-cpu 500m --limits-memory 1024Mi
|
kn service update svc --requests-cpu 500m --limits-memory 1024Mi
|
||||||
|
|
||||||
|
# Assign tag 'latest' and 'stable' to revisions 'echo-v2' and 'echo-v1' respectively
|
||||||
|
kn service update svc --tag echo-v2=latest --tag echo-v1=stable
|
||||||
|
OR
|
||||||
|
kn service update svc --tag echo-v2=latest,echo-v1=stable
|
||||||
|
|
||||||
|
# Update tag from 'testing' to 'staging' for latest ready revision of service
|
||||||
|
kn service update svc --untag testing --tag @latest=staging
|
||||||
|
|
||||||
|
# Add tag 'test' to echo-v3 revision with 10% traffic and rest to latest ready revision of service
|
||||||
|
kn service update svc --tag echo-v3=test --traffic test=10,@latest=90
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
@ -43,6 +54,9 @@ kn service update NAME [flags]
|
||||||
--requests-cpu string The requested CPU (e.g., 250m).
|
--requests-cpu string The requested CPU (e.g., 250m).
|
||||||
--requests-memory string The requested memory (e.g., 64Mi).
|
--requests-memory string The requested memory (e.g., 64Mi).
|
||||||
--revision-name string The revision name to set. Must start with the service name and a dash as a prefix. Empty revision name will result in the server generating a name for the revision. Accepts golang templates, allowing {{.Service}} for the service name, {{.Generation}} for the generation, and {{.Random [n]}} for n random consonants. (default "{{.Service}}-{{.Random 5}}-{{.Generation}}")
|
--revision-name string The revision name to set. Must start with the service name and a dash as a prefix. Empty revision name will result in the server generating a name for the revision. Accepts golang templates, allowing {{.Service}} for the service name, {{.Generation}} for the generation, and {{.Random [n]}} for n random consonants. (default "{{.Service}}-{{.Random 5}}-{{.Generation}}")
|
||||||
|
--tag strings Set tag (format: --tag revisionRef=tagName) where revisionRef can be a revision or '@latest' string representing latest ready revision. This flag can be specified multiple times.
|
||||||
|
--traffic strings Set traffic distribution (format: --traffic revisionRef=percent) where revisionRef can be a revision or a tag or '@latest' string representing latest ready revision. This flag can be given multiple times with percent summing up to 100%.
|
||||||
|
--untag strings Untag revision (format: --untag tagName). This flag can be spcified multiple times.
|
||||||
--wait-timeout int Seconds to wait before giving up on waiting for service to be ready. (default 60)
|
--wait-timeout int Seconds to wait before giving up on waiting for service to be ready. (default 60)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
// 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 flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Traffic struct {
|
||||||
|
RevisionsPercentages []string
|
||||||
|
RevisionsTags []string
|
||||||
|
UntagRevisions []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Traffic) Add(cmd *cobra.Command) {
|
||||||
|
cmd.Flags().StringSliceVar(&t.RevisionsPercentages,
|
||||||
|
"traffic",
|
||||||
|
nil,
|
||||||
|
"Set traffic distribution (format: --traffic revisionRef=percent) where revisionRef can be a revision or a tag or '@latest' string "+
|
||||||
|
"representing latest ready revision. This flag can be given multiple times with percent summing up to 100%.")
|
||||||
|
|
||||||
|
cmd.Flags().StringSliceVar(&t.RevisionsTags,
|
||||||
|
"tag",
|
||||||
|
nil,
|
||||||
|
"Set tag (format: --tag revisionRef=tagName) where revisionRef can be a revision or '@latest' string representing latest ready revision. "+
|
||||||
|
"This flag can be specified multiple times.")
|
||||||
|
|
||||||
|
cmd.Flags().StringSliceVar(&t.UntagRevisions,
|
||||||
|
"untag",
|
||||||
|
nil,
|
||||||
|
"Untag revision (format: --untag tagName). This flag can be spcified multiple times.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Traffic) PercentagesChanged(cmd *cobra.Command) bool {
|
||||||
|
return cmd.Flags().Changed("traffic")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Traffic) TagsChanged(cmd *cobra.Command) bool {
|
||||||
|
return cmd.Flags().Changed("tag") || cmd.Flags().Changed("untag")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Traffic) Changed(cmd *cobra.Command) bool {
|
||||||
|
return t.PercentagesChanged(cmd) || t.TagsChanged(cmd)
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/knative/client/pkg/kn/commands/flags"
|
||||||
|
"github.com/knative/client/pkg/kn/traffic"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
api_errors "k8s.io/apimachinery/pkg/api/errors"
|
api_errors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
|
||||||
|
|
@ -27,19 +29,30 @@ import (
|
||||||
func NewServiceUpdateCommand(p *commands.KnParams) *cobra.Command {
|
func NewServiceUpdateCommand(p *commands.KnParams) *cobra.Command {
|
||||||
var editFlags ConfigurationEditFlags
|
var editFlags ConfigurationEditFlags
|
||||||
var waitFlags commands.WaitFlags
|
var waitFlags commands.WaitFlags
|
||||||
|
var trafficFlags flags.Traffic
|
||||||
serviceUpdateCommand := &cobra.Command{
|
serviceUpdateCommand := &cobra.Command{
|
||||||
Use: "update NAME [flags]",
|
Use: "update NAME [flags]",
|
||||||
Short: "Update a service.",
|
Short: "Update a service.",
|
||||||
Example: `
|
Example: `
|
||||||
# Updates a service 'mysvc' with new environment variables
|
# Updates a service 'svc' with new environment variables
|
||||||
kn service update mysvc --env KEY1=VALUE1 --env KEY2=VALUE2
|
kn service update svc --env KEY1=VALUE1 --env KEY2=VALUE2
|
||||||
|
|
||||||
# Update a service 'mysvc' with new port
|
# Update a service 'svc' with new port
|
||||||
kn service update mysvc --port 80
|
kn service update svc --port 80
|
||||||
|
|
||||||
# Updates a service 'mysvc' with new requests and limits parameters
|
# Updates a service 'svc' with new requests and limits parameters
|
||||||
kn service update mysvc --requests-cpu 500m --limits-memory 1024Mi`,
|
kn service update svc --requests-cpu 500m --limits-memory 1024Mi
|
||||||
|
|
||||||
|
# Assign tag 'latest' and 'stable' to revisions 'echo-v2' and 'echo-v1' respectively
|
||||||
|
kn service update svc --tag echo-v2=latest --tag echo-v1=stable
|
||||||
|
OR
|
||||||
|
kn service update svc --tag echo-v2=latest,echo-v1=stable
|
||||||
|
|
||||||
|
# Update tag from 'testing' to 'staging' for latest ready revision of service
|
||||||
|
kn service update svc --untag testing --tag @latest=staging
|
||||||
|
|
||||||
|
# Add tag 'test' to echo-v3 revision with 10% traffic and rest to latest ready revision of service
|
||||||
|
kn service update svc --tag echo-v3=test --traffic test=10,@latest=90`,
|
||||||
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
RunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
return errors.New("requires the service name.")
|
return errors.New("requires the service name.")
|
||||||
|
|
@ -69,6 +82,15 @@ func NewServiceUpdateCommand(p *commands.KnParams) *cobra.Command {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if trafficFlags.Changed(cmd) {
|
||||||
|
traffic, err := traffic.Compute(cmd, service.Spec.Traffic, &trafficFlags)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
service.Spec.Traffic = traffic
|
||||||
|
}
|
||||||
|
|
||||||
err = client.UpdateService(service)
|
err = client.UpdateService(service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Retry to update when a resource version conflict exists
|
// Retry to update when a resource version conflict exists
|
||||||
|
|
@ -99,6 +121,7 @@ func NewServiceUpdateCommand(p *commands.KnParams) *cobra.Command {
|
||||||
commands.AddNamespaceFlags(serviceUpdateCommand.Flags(), false)
|
commands.AddNamespaceFlags(serviceUpdateCommand.Flags(), false)
|
||||||
editFlags.AddUpdateFlags(serviceUpdateCommand)
|
editFlags.AddUpdateFlags(serviceUpdateCommand)
|
||||||
waitFlags.AddConditionWaitFlags(serviceUpdateCommand, 60, "Update", "service")
|
waitFlags.AddConditionWaitFlags(serviceUpdateCommand, 60, "Update", "service")
|
||||||
|
trafficFlags.Add(serviceUpdateCommand)
|
||||||
return serviceUpdateCommand
|
return serviceUpdateCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,369 @@
|
||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/knative/client/pkg/kn/commands/flags"
|
||||||
|
"github.com/knative/pkg/ptr"
|
||||||
|
"github.com/knative/serving/pkg/apis/serving/v1alpha1"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var latestRevisionRef = "@latest"
|
||||||
|
|
||||||
|
// ServiceTraffic type for operating on service traffic targets
|
||||||
|
type ServiceTraffic []v1alpha1.TrafficTarget
|
||||||
|
|
||||||
|
func newServiceTraffic(traffic []v1alpha1.TrafficTarget) ServiceTraffic {
|
||||||
|
return ServiceTraffic(traffic)
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitByEqualSign(pair string) (string, string, error) {
|
||||||
|
parts := strings.Split(pair, "=")
|
||||||
|
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
|
||||||
|
return "", "", fmt.Errorf("expecting the value format in value1=value2, given %s", pair)
|
||||||
|
}
|
||||||
|
return parts[0], strings.TrimSuffix(parts[1], "%"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTarget(tag, revision string, percent int, latestRevision bool) (target v1alpha1.TrafficTarget) {
|
||||||
|
target.Percent = percent
|
||||||
|
target.Tag = tag
|
||||||
|
if latestRevision {
|
||||||
|
target.LatestRevision = ptr.Bool(true)
|
||||||
|
} else {
|
||||||
|
// as LatestRevision and RevisionName can't be specfied together for a target
|
||||||
|
target.LatestRevision = ptr.Bool(false)
|
||||||
|
target.RevisionName = revision
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ServiceTraffic) isTagPresentOnRevision(tag, revision string) bool {
|
||||||
|
for _, target := range e {
|
||||||
|
if target.Tag == tag && target.RevisionName == revision {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ServiceTraffic) tagOfLatestReadyRevision() string {
|
||||||
|
for _, target := range e {
|
||||||
|
if *target.LatestRevision {
|
||||||
|
return target.Tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ServiceTraffic) isTagPresent(tag string) bool {
|
||||||
|
for _, target := range e {
|
||||||
|
if target.Tag == tag {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ServiceTraffic) untagRevision(tag string) {
|
||||||
|
for i, target := range e {
|
||||||
|
if target.Tag == tag {
|
||||||
|
e[i].Tag = ""
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ServiceTraffic) isRevisionPresent(revision string) bool {
|
||||||
|
for _, target := range e {
|
||||||
|
if target.RevisionName == revision {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ServiceTraffic) isLatestRevisionTrue() bool {
|
||||||
|
for _, target := range e {
|
||||||
|
if *target.LatestRevision == true {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// TagRevision assigns given tag to a revision
|
||||||
|
func (e ServiceTraffic) TagRevision(tag, revision string) ServiceTraffic {
|
||||||
|
for i, target := range e {
|
||||||
|
if target.RevisionName == revision {
|
||||||
|
if target.Tag != "" { // referenced revision is requested to have multiple tags
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
e[i].Tag = tag // referenced revision doesn't have tag, tag it
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// append a new target if revision doesn't exist in traffic block
|
||||||
|
// or if referenced revision is requested to have multiple tags
|
||||||
|
e = append(e, newTarget(tag, revision, 0, false))
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// TagLatestRevision assigns given tag to latest ready revision
|
||||||
|
func (e ServiceTraffic) TagLatestRevision(tag string) ServiceTraffic {
|
||||||
|
for i, target := range e {
|
||||||
|
if *target.LatestRevision {
|
||||||
|
e[i].Tag = tag
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e = append(e, newTarget(tag, "", 0, true))
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTrafficByRevision checks given revision in existing traffic block and sets given percent if found
|
||||||
|
func (e ServiceTraffic) SetTrafficByRevision(revision string, percent int) {
|
||||||
|
for i, target := range e {
|
||||||
|
if target.RevisionName == revision {
|
||||||
|
e[i].Percent = percent
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTrafficByTag checks given tag in existing traffic block and sets given percent if found
|
||||||
|
func (e ServiceTraffic) SetTrafficByTag(tag string, percent int) {
|
||||||
|
for i, target := range e {
|
||||||
|
if target.Tag == tag {
|
||||||
|
e[i].Percent = percent
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTrafficByLatestRevision sets given percent to latest ready revision of service
|
||||||
|
func (e ServiceTraffic) SetTrafficByLatestRevision(percent int) {
|
||||||
|
for i, target := range e {
|
||||||
|
if *target.LatestRevision {
|
||||||
|
e[i].Percent = percent
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetAllTargetPercent resets (0) 'Percent' field for all the traffic targets
|
||||||
|
func (e ServiceTraffic) ResetAllTargetPercent() {
|
||||||
|
for i := range e {
|
||||||
|
e[i].Percent = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveNullTargets removes targets from traffic block if they don't have and 0 percent traffic
|
||||||
|
func (e ServiceTraffic) RemoveNullTargets() (newTraffic ServiceTraffic) {
|
||||||
|
for _, target := range e {
|
||||||
|
if target.Tag == "" && target.Percent == 0 {
|
||||||
|
} else {
|
||||||
|
newTraffic = append(newTraffic, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newTraffic
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorOverWritingtagOfLatestReadyRevision(existingTag, requestedTag string) error {
|
||||||
|
return fmt.Errorf("tag '%s' exists on latest ready revision of service, "+
|
||||||
|
"refusing to overwrite existing tag with '%s', "+
|
||||||
|
"add flag '--untag %s' in command to untag it", existingTag, requestedTag, existingTag)
|
||||||
|
}
|
||||||
|
func errorOverWritingTag(tag string) error {
|
||||||
|
return fmt.Errorf("refusing to overwrite existing tag in service, "+
|
||||||
|
"add flag '--untag %s' in command to untag it", tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorRepeatingRevision(forFlag string, name string) error {
|
||||||
|
if name == latestRevisionRef {
|
||||||
|
name = "identifier " + latestRevisionRef
|
||||||
|
} else {
|
||||||
|
name = "revision reference " + name
|
||||||
|
}
|
||||||
|
return fmt.Errorf("repetition of %s "+
|
||||||
|
"is not allowed, use only once with %s flag", name, forFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifies if user has repeated @latest field in --tag or --traffic flags
|
||||||
|
// verifyInputSanity checks:
|
||||||
|
// - if user has repeated @latest field in --tag or --traffic flags
|
||||||
|
// - if provided traffic portion are integers
|
||||||
|
func verifyInputSanity(trafficFlags *flags.Traffic) error {
|
||||||
|
var latestRevisionTag = false
|
||||||
|
var sum = 0
|
||||||
|
|
||||||
|
for _, each := range trafficFlags.RevisionsTags {
|
||||||
|
revision, _, err := splitByEqualSign(each)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if latestRevisionTag && revision == latestRevisionRef {
|
||||||
|
return errorRepeatingRevision("--tag", latestRevisionRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
if revision == latestRevisionRef {
|
||||||
|
latestRevisionTag = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
revisionRefMap := make(map[string]int)
|
||||||
|
for i, each := range trafficFlags.RevisionsPercentages {
|
||||||
|
revisionRef, percent, err := splitByEqualSign(each)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// To check if there are duplicate revision names in traffic flags
|
||||||
|
if _, exist := revisionRefMap[revisionRef]; exist {
|
||||||
|
return errorRepeatingRevision("--traffic", revisionRef)
|
||||||
|
} else {
|
||||||
|
revisionRefMap[revisionRef] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
percentInt, err := strconv.Atoi(percent)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error converting given %s to integer value for traffic distribution", percent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if percentInt < 0 || percentInt > 100 {
|
||||||
|
return fmt.Errorf("invalid value for traffic percent %d, expected 0 <= percent <= 100", percentInt)
|
||||||
|
}
|
||||||
|
|
||||||
|
sum += percentInt
|
||||||
|
}
|
||||||
|
|
||||||
|
// equivalent check for `cmd.Flags().Changed("traffic")` as we don't have `cmd` in this function
|
||||||
|
if len(trafficFlags.RevisionsPercentages) > 0 && sum != 100 {
|
||||||
|
return fmt.Errorf("given traffic percents sum to %d, want 100", sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute takes service traffic targets and updates per given traffic flags
|
||||||
|
func Compute(cmd *cobra.Command, targets []v1alpha1.TrafficTarget, trafficFlags *flags.Traffic) ([]v1alpha1.TrafficTarget, error) {
|
||||||
|
err := verifyInputSanity(trafficFlags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
traffic := newServiceTraffic(targets)
|
||||||
|
|
||||||
|
// First precedence: Untag revisions
|
||||||
|
for _, tag := range trafficFlags.UntagRevisions {
|
||||||
|
traffic.untagRevision(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, each := range trafficFlags.RevisionsTags {
|
||||||
|
revision, tag, _ := splitByEqualSign(each) // err is checked in verifyInputSanity
|
||||||
|
|
||||||
|
// Second precedence: Tag latestRevision
|
||||||
|
if revision == latestRevisionRef {
|
||||||
|
existingTagOnLatestRevision := traffic.tagOfLatestReadyRevision()
|
||||||
|
|
||||||
|
// just pass if existing == requested
|
||||||
|
if existingTagOnLatestRevision == tag {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply requested tag only if it doesnt exist in traffic block
|
||||||
|
if traffic.isTagPresent(tag) {
|
||||||
|
return nil, errorOverWritingTag(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existingTagOnLatestRevision == "" {
|
||||||
|
traffic = traffic.TagLatestRevision(tag)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
return nil, errorOverWritingtagOfLatestReadyRevision(existingTagOnLatestRevision, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third precedence: Tag other revisions
|
||||||
|
// dont throw error if the tag present == requested tag
|
||||||
|
if traffic.isTagPresentOnRevision(tag, revision) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// error if the tag is assigned to some other revision
|
||||||
|
if traffic.isTagPresent(tag) {
|
||||||
|
return nil, errorOverWritingTag(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
traffic = traffic.TagRevision(tag, revision)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.Flags().Changed("traffic") {
|
||||||
|
// reset existing traffic portions as what's on CLI is desired state of traffic split portions
|
||||||
|
traffic.ResetAllTargetPercent()
|
||||||
|
|
||||||
|
for _, each := range trafficFlags.RevisionsPercentages {
|
||||||
|
// revisionRef works here as either revision or tag as either can be specified on CLI
|
||||||
|
revisionRef, percent, _ := splitByEqualSign(each) // err is verified in verifyInputSanity
|
||||||
|
percentInt, _ := strconv.Atoi(percent) // percentInt (for int) is verified in verifyInputSanity
|
||||||
|
|
||||||
|
// fourth precedence: set traffic for latest revision
|
||||||
|
if revisionRef == latestRevisionRef {
|
||||||
|
if traffic.isLatestRevisionTrue() {
|
||||||
|
traffic.SetTrafficByLatestRevision(percentInt)
|
||||||
|
} else {
|
||||||
|
// if no latestRevision ref is present in traffic block
|
||||||
|
traffic = append(traffic, newTarget("", "", percentInt, true))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// fifth precedence: set traffic for rest of revisions
|
||||||
|
// If in a traffic block, revisionName of one target == tag of another,
|
||||||
|
// one having tag is assigned given percent, as tags are supposed to be unique
|
||||||
|
// and should be used (in this case) to avoid ambiguity
|
||||||
|
|
||||||
|
// first check if given revisionRef is a tag
|
||||||
|
if traffic.isTagPresent(revisionRef) {
|
||||||
|
traffic.SetTrafficByTag(revisionRef, percentInt)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if given revisionRef is a revision
|
||||||
|
if traffic.isRevisionPresent(revisionRef) {
|
||||||
|
traffic.SetTrafficByRevision(revisionRef, percentInt)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Check at serving level, improve error
|
||||||
|
//if !RevisionExists(revisionRef) {
|
||||||
|
// return error.New("Revision/Tag %s does not exists in traffic block.")
|
||||||
|
//}
|
||||||
|
|
||||||
|
// provided revisionRef isn't present in traffic block, add it
|
||||||
|
traffic = append(traffic, newTarget("", revisionRef, percentInt, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// remove any targets having no tags and 0% traffic portion
|
||||||
|
return traffic.RemoveNullTargets(), nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,289 @@
|
||||||
|
// 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -59,6 +59,5 @@ initialize $@
|
||||||
|
|
||||||
header "Running tests for Knative serving $KNATIVE_VERSION"
|
header "Running tests for Knative serving $KNATIVE_VERSION"
|
||||||
|
|
||||||
go_test_e2e ./test/e2e || fail_test
|
go_test_e2e -timeout=30m ./test/e2e || fail_test
|
||||||
|
|
||||||
success
|
success
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -72,6 +73,14 @@ func getNamespaceCountAndIncrement() int {
|
||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getServiceNameAndIncrement(base string) string {
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
current := serviceCount
|
||||||
|
serviceCount++
|
||||||
|
return base + strconv.Itoa(current)
|
||||||
|
}
|
||||||
|
|
||||||
// Teardown clean up
|
// Teardown clean up
|
||||||
func (test *e2eTest) Teardown(t *testing.T) {
|
func (test *e2eTest) Teardown(t *testing.T) {
|
||||||
test.DeleteTestNamespace(t, test.env.Namespace)
|
test.DeleteTestNamespace(t, test.env.Namespace)
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,10 @@ type env struct {
|
||||||
|
|
||||||
const defaultKnE2ENamespace = "kne2etests"
|
const defaultKnE2ENamespace = "kne2etests"
|
||||||
|
|
||||||
var namespaceCount = 0
|
var (
|
||||||
|
namespaceCount = 0
|
||||||
|
serviceCount = 0
|
||||||
|
)
|
||||||
|
|
||||||
func buildEnv(t *testing.T) env {
|
func buildEnv(t *testing.T) env {
|
||||||
env := env{
|
env := env{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,389 @@
|
||||||
|
// 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 im
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build e2e
|
||||||
|
|
||||||
|
package e2e
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/knative/client/pkg/util"
|
||||||
|
"gotest.tools/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var targetsSeparator = "|"
|
||||||
|
var targetFieldsSeparator = ","
|
||||||
|
var targetFieldsLength = 4
|
||||||
|
|
||||||
|
// returns deployed service targets separated by '|' and each target fields seprated by comma
|
||||||
|
var targetsJsonPath = "jsonpath={range .status.traffic[*]}{.tag}{','}{.revisionName}{','}{.percent}{','}{.latestRevision}{'|'}{end}"
|
||||||
|
|
||||||
|
// returns deployed service latest revision name
|
||||||
|
var latestRevisionJsonPath = "jsonpath={.status.traffic[?(@.latestRevision==true)].revisionName}"
|
||||||
|
|
||||||
|
// TargetFileds are used in e2e to store expected fields per traffic target
|
||||||
|
// and actual traffic targets fields of deployed service are converted into struct before comparing
|
||||||
|
type TargetFields struct {
|
||||||
|
Tag string
|
||||||
|
Revision string
|
||||||
|
Percent int
|
||||||
|
Latest bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTargetFields(tag, revision string, percent int, latest bool) TargetFields {
|
||||||
|
return TargetFields{tag, revision, percent, latest}
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitTargets(s, separator string, partsCount int) ([]string, error) {
|
||||||
|
parts := strings.SplitN(s, separator, partsCount)
|
||||||
|
if len(parts) != partsCount {
|
||||||
|
return nil, errors.New(fmt.Sprintf("expecting to receive parts of length %d, got %d "+
|
||||||
|
"string: %s seprator: %s", partsCount, len(parts), s, separator))
|
||||||
|
}
|
||||||
|
return parts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatActualTargets takes the traffic targets string received after jsonpath operation and converts
|
||||||
|
// them into []TargetFields for comparison
|
||||||
|
func formatActualTargets(t *testing.T, actualTargets []string) (formattedTargets []TargetFields) {
|
||||||
|
for _, each := range actualTargets {
|
||||||
|
each := strings.TrimSuffix(each, targetFieldsSeparator)
|
||||||
|
fields, err := splitTargets(each, targetFieldsSeparator, targetFieldsLength)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
percentInt, err := strconv.Atoi(fields[2])
|
||||||
|
assert.NilError(t, err)
|
||||||
|
latestBool, err := strconv.ParseBool(fields[3])
|
||||||
|
assert.NilError(t, err)
|
||||||
|
formattedTargets = append(formattedTargets, newTargetFields(fields[0], fields[1], percentInt, latestBool))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestTrafficSplit runs different e2e tests for service traffic splitting and verifies the traffic targets from service status
|
||||||
|
func TestTrafficSplit(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
test := NewE2eTest(t)
|
||||||
|
test.Setup(t)
|
||||||
|
defer test.Teardown(t)
|
||||||
|
|
||||||
|
serviceBase := "echo"
|
||||||
|
t.Run("tag two revisions as v1 and v2 and give 50-50% share",
|
||||||
|
func(t *testing.T) {
|
||||||
|
serviceName := getServiceNameAndIncrement(serviceBase)
|
||||||
|
test.serviceCreate(t, serviceName)
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, []string{"--env", "TARGET=v1"})
|
||||||
|
rev1 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, []string{"--env", "TARGET=v2"})
|
||||||
|
rev2 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
tflags := []string{"--tag", fmt.Sprintf("%s=v1,%s=v2", rev1, rev2),
|
||||||
|
"--traffic", "v1=50,v2=50"}
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
// make ordered fields per tflags (tag, revision, percent, latest)
|
||||||
|
expectedTargets := []TargetFields{newTargetFields("v1", rev1, 50, false), newTargetFields("v2", rev2, 50, false)}
|
||||||
|
test.verifyTargets(t, serviceName, expectedTargets)
|
||||||
|
test.serviceDelete(t, serviceName)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
t.Run("ramp/up down a revision to 20% adjusting other traffic to accommodate",
|
||||||
|
func(t *testing.T) {
|
||||||
|
serviceName := getServiceNameAndIncrement(serviceBase)
|
||||||
|
test.serviceCreate(t, serviceName)
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, []string{"--env", "TARGET=v1"})
|
||||||
|
rev1 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, []string{"--env", "TARGET=v2"})
|
||||||
|
rev2 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
tflags := []string{"--traffic", fmt.Sprintf("%s=20,%s=80", rev1, rev2)} // traffic by revision name
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
expectedTargets := []TargetFields{newTargetFields("", rev1, 20, false), newTargetFields("", rev2, 80, false)}
|
||||||
|
test.verifyTargets(t, serviceName, expectedTargets)
|
||||||
|
test.serviceDelete(t, serviceName)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
t.Run("tag a revision as candidate, without otherwise changing any traffic split",
|
||||||
|
func(t *testing.T) {
|
||||||
|
serviceName := getServiceNameAndIncrement(serviceBase)
|
||||||
|
test.serviceCreate(t, serviceName)
|
||||||
|
rev1 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, []string{"--env", "TARGET=v1"})
|
||||||
|
rev2 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
tflags := []string{"--tag", fmt.Sprintf("%s=%s", rev1, "candidate")} // no traffic, append new target with tag in traffic block
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
expectedTargets := []TargetFields{newTargetFields("", rev2, 100, true), newTargetFields("candidate", rev1, 0, false)}
|
||||||
|
test.verifyTargets(t, serviceName, expectedTargets)
|
||||||
|
test.serviceDelete(t, serviceName)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
t.Run("tag a revision as candidate, set 2% traffic adjusting other traffic to accommodate",
|
||||||
|
func(t *testing.T) {
|
||||||
|
serviceName := getServiceNameAndIncrement(serviceBase)
|
||||||
|
test.serviceCreate(t, serviceName)
|
||||||
|
rev1 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, []string{"--env", "TARGET=v1"})
|
||||||
|
rev2 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
tflags := []string{"--tag", fmt.Sprintf("%s=%s", rev1, "candidate"),
|
||||||
|
"--traffic", "candidate=2%,@latest=98%"} // traffic by tag name and use % at the end
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
expectedTargets := []TargetFields{newTargetFields("", rev2, 98, true), newTargetFields("candidate", rev1, 2, false)}
|
||||||
|
test.verifyTargets(t, serviceName, expectedTargets)
|
||||||
|
test.serviceDelete(t, serviceName)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
t.Run("update tag for a revision from candidate to current, tag current is present on another revision",
|
||||||
|
func(t *testing.T) {
|
||||||
|
serviceName := getServiceNameAndIncrement(serviceBase)
|
||||||
|
// make available 3 revisions for service first
|
||||||
|
test.serviceCreate(t, serviceName)
|
||||||
|
rev1 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, []string{"--env", "TARGET=v2"})
|
||||||
|
rev2 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, []string{"--env", "TARGET=v3"}) //note that this gives 100% traffic to latest revision (rev3)
|
||||||
|
rev3 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
// make existing state: tag current and candidate exist in traffic block
|
||||||
|
tflags := []string{"--tag", fmt.Sprintf("%s=current,%s=candidate", rev1, rev2)}
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
// desired state of tags: update tag of revision (rev2) from candidate to current (which is present on rev1)
|
||||||
|
tflags = []string{"--untag", "current,candidate", "--tag", fmt.Sprintf("%s=current", rev2)} //untag first to update
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
// there will be 2 targets in existing block 1. @latest, 2.for revision $rev2
|
||||||
|
// target for rev1 is removed as it had no traffic and we untagged it's tag current
|
||||||
|
expectedTargets := []TargetFields{newTargetFields("", rev3, 100, true), newTargetFields("current", rev2, 0, false)}
|
||||||
|
test.verifyTargets(t, serviceName, expectedTargets)
|
||||||
|
test.serviceDelete(t, serviceName)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
t.Run("update tag from testing to staging for @latest revision",
|
||||||
|
func(t *testing.T) {
|
||||||
|
serviceName := getServiceNameAndIncrement(serviceBase)
|
||||||
|
test.serviceCreate(t, serviceName)
|
||||||
|
rev1 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
// make existing state: tag @latest as testing
|
||||||
|
tflags := []string{"--tag", "@latest=testing"}
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
// desired state: change tag from testing to staging
|
||||||
|
tflags = []string{"--untag", "testing", "--tag", "@latest=staging"}
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
expectedTargets := []TargetFields{newTargetFields("staging", rev1, 100, true)}
|
||||||
|
test.verifyTargets(t, serviceName, expectedTargets)
|
||||||
|
test.serviceDelete(t, serviceName)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
t.Run("update tag from testing to staging for a revision (non @latest)",
|
||||||
|
func(t *testing.T) {
|
||||||
|
serviceName := getServiceNameAndIncrement(serviceBase)
|
||||||
|
test.serviceCreate(t, serviceName)
|
||||||
|
rev1 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, []string{"--env", "TARGET=v2"})
|
||||||
|
rev2 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
// make existing state: tag a revision as testing
|
||||||
|
tflags := []string{"--tag", fmt.Sprintf("%s=testing", rev1)}
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
// desired state: change tag from testing to staging
|
||||||
|
tflags = []string{"--untag", "testing", "--tag", fmt.Sprintf("%s=staging", rev1)}
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
expectedTargets := []TargetFields{newTargetFields("", rev2, 100, true),
|
||||||
|
newTargetFields("staging", rev1, 0, false)}
|
||||||
|
test.verifyTargets(t, serviceName, expectedTargets)
|
||||||
|
test.serviceDelete(t, serviceName)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
// test reducing number of targets from traffic blockdd
|
||||||
|
t.Run("remove a revision with tag old from traffic block entierly",
|
||||||
|
func(t *testing.T) {
|
||||||
|
serviceName := getServiceNameAndIncrement(serviceBase)
|
||||||
|
test.serviceCreate(t, serviceName)
|
||||||
|
rev1 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, []string{"--env", "TARGET=v2"})
|
||||||
|
rev2 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
// existing state: traffic block having a revision with tag old and some traffic
|
||||||
|
tflags := []string{"--tag", fmt.Sprintf("%s=old", rev1),
|
||||||
|
"--traffic", "old=2,@latest=98"}
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
// desired state: remove revision with tag old
|
||||||
|
tflags = []string{"--untag", "old", "--traffic", "@latest=100"}
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
expectedTargets := []TargetFields{newTargetFields("", rev2, 100, true)}
|
||||||
|
test.verifyTargets(t, serviceName, expectedTargets)
|
||||||
|
test.serviceDelete(t, serviceName)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
t.Run("tag a revision as stable and current with 50-50% traffic",
|
||||||
|
func(t *testing.T) {
|
||||||
|
serviceName := getServiceNameAndIncrement(serviceBase)
|
||||||
|
test.serviceCreate(t, serviceName)
|
||||||
|
rev1 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
// existing state: traffic block having two targets
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, []string{"--env", "TARGET=v2"})
|
||||||
|
|
||||||
|
// desired state: tag non-@latest revision with two tags and 50-50% traffic each
|
||||||
|
tflags := []string{"--tag", fmt.Sprintf("%s=stable,%s=current", rev1, rev1),
|
||||||
|
"--traffic", "stable=50%,current=50%"}
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
expectedTargets := []TargetFields{newTargetFields("stable", rev1, 50, false), newTargetFields("current", rev1, 50, false)}
|
||||||
|
test.verifyTargets(t, serviceName, expectedTargets)
|
||||||
|
test.serviceDelete(t, serviceName)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
t.Run("revert all traffic to latest ready revision of service",
|
||||||
|
func(t *testing.T) {
|
||||||
|
serviceName := getServiceNameAndIncrement(serviceBase)
|
||||||
|
test.serviceCreate(t, serviceName)
|
||||||
|
rev1 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, []string{"--env", "TARGET=v2"})
|
||||||
|
rev2 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
// existing state: latest revision not getting any traffic
|
||||||
|
tflags := []string{"--traffic", fmt.Sprintf("%s=100", rev1)}
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
// desired state: revert traffic to latest revision
|
||||||
|
tflags = []string{"--traffic", "@latest=100"}
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
expectedTargets := []TargetFields{newTargetFields("", rev2, 100, true)}
|
||||||
|
test.verifyTargets(t, serviceName, expectedTargets)
|
||||||
|
test.serviceDelete(t, serviceName)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
t.Run("tag latest ready revision of service as current",
|
||||||
|
func(t *testing.T) {
|
||||||
|
serviceName := getServiceNameAndIncrement(serviceBase)
|
||||||
|
// existing state: latest revision has no tag
|
||||||
|
test.serviceCreate(t, serviceName)
|
||||||
|
rev1 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
// desired state: tag current to latest ready revision
|
||||||
|
tflags := []string{"--tag", "@latest=current"}
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
expectedTargets := []TargetFields{newTargetFields("current", rev1, 100, true)}
|
||||||
|
test.verifyTargets(t, serviceName, expectedTargets)
|
||||||
|
test.serviceDelete(t, serviceName)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
t.Run("update tag for a revision as testing and assign all the traffic to it:",
|
||||||
|
func(t *testing.T) {
|
||||||
|
serviceName := getServiceNameAndIncrement(serviceBase)
|
||||||
|
test.serviceCreate(t, serviceName)
|
||||||
|
rev1 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, []string{"--env", "TARGET=v2"})
|
||||||
|
rev2 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
// existing state: two revision exists with traffic share and
|
||||||
|
// each revision has tag and traffic portions
|
||||||
|
tflags := []string{"--tag", fmt.Sprintf("@latest=current,%s=candidate", rev1),
|
||||||
|
"--traffic", "current=90,candidate=10"}
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
// desired state: update tag for rev1 as testing (from candidate) with 100% traffic
|
||||||
|
tflags = []string{"--untag", "candidate", "--tag", fmt.Sprintf("%s=testing", rev1),
|
||||||
|
"--traffic", "testing=100"}
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
expectedTargets := []TargetFields{newTargetFields("current", rev2, 0, true),
|
||||||
|
newTargetFields("testing", rev1, 100, false)}
|
||||||
|
test.verifyTargets(t, serviceName, expectedTargets)
|
||||||
|
test.serviceDelete(t, serviceName)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
t.Run("replace latest tag of a revision with old and give latest to another revision",
|
||||||
|
func(t *testing.T) {
|
||||||
|
serviceName := getServiceNameAndIncrement(serviceBase)
|
||||||
|
test.serviceCreate(t, serviceName)
|
||||||
|
rev1 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, []string{"--env", "TARGET=v2"})
|
||||||
|
rev2 := test.latestRevisionOfService(t, serviceName)
|
||||||
|
|
||||||
|
// existing state: a revision exist with latest tag
|
||||||
|
tflags := []string{"--tag", fmt.Sprintf("%s=latest", rev1)}
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
// desired state of revision tags: rev1=old rev2=latest
|
||||||
|
tflags = []string{"--untag", "latest", "--tag", fmt.Sprintf("%s=old,%s=latest", rev1, rev2)}
|
||||||
|
test.serviceUpdateWithOptions(t, serviceName, tflags)
|
||||||
|
|
||||||
|
expectedTargets := []TargetFields{newTargetFields("", rev2, 100, true),
|
||||||
|
newTargetFields("old", rev1, 0, false),
|
||||||
|
// Tagging by revision name adds a new target even though latestReadyRevision==rev2,
|
||||||
|
// because we didn't refer @latest reference, but explcit name of revision.
|
||||||
|
// In spec of traffic block (not status) either latestReadyRevision:true or revisionName can be given per target
|
||||||
|
newTargetFields("latest", rev2, 0, false)}
|
||||||
|
|
||||||
|
test.verifyTargets(t, serviceName, expectedTargets)
|
||||||
|
test.serviceDelete(t, serviceName)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (test *e2eTest) verifyTargets(t *testing.T, serviceName string, expectedTargets []TargetFields) {
|
||||||
|
out := test.serviceDescribeWithJsonPath(t, serviceName, targetsJsonPath)
|
||||||
|
assert.Check(t, out != "")
|
||||||
|
out = strings.TrimSuffix(out, targetsSeparator)
|
||||||
|
actualTargets, err := splitTargets(out, targetsSeparator, len(expectedTargets))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
formattedActualTargets := formatActualTargets(t, actualTargets)
|
||||||
|
assert.DeepEqual(t, expectedTargets, formattedActualTargets)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (test *e2eTest) latestRevisionOfService(t *testing.T, serviceName string) string {
|
||||||
|
return test.serviceDescribeWithJsonPath(t, serviceName, latestRevisionJsonPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (test *e2eTest) serviceDescribeWithJsonPath(t *testing.T, serviceName, jsonpath string) string {
|
||||||
|
command := []string{"service", "describe", serviceName, "-o", jsonpath}
|
||||||
|
out, err := test.kn.RunWithOpts(command, runOpts{})
|
||||||
|
assert.NilError(t, err)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (test *e2eTest) serviceUpdateWithOptions(t *testing.T, serviceName string, options []string) {
|
||||||
|
command := []string{"service", "update", serviceName}
|
||||||
|
command = append(command, options...)
|
||||||
|
out, err := test.kn.RunWithOpts(command, runOpts{NoNamespace: false})
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Check(t, util.ContainsAll(out, "Service", serviceName, "update", "namespace", test.kn.namespace))
|
||||||
|
}
|
||||||
|
|
@ -66,11 +66,11 @@ github.com/knative/build/pkg/apis/build
|
||||||
# github.com/knative/pkg v0.0.0-20190617142447-13b093adc272
|
# github.com/knative/pkg v0.0.0-20190617142447-13b093adc272
|
||||||
github.com/knative/pkg/apis
|
github.com/knative/pkg/apis
|
||||||
github.com/knative/pkg/apis/duck/v1beta1
|
github.com/knative/pkg/apis/duck/v1beta1
|
||||||
|
github.com/knative/pkg/ptr
|
||||||
github.com/knative/pkg/kmp
|
github.com/knative/pkg/kmp
|
||||||
github.com/knative/pkg/apis/duck
|
github.com/knative/pkg/apis/duck
|
||||||
github.com/knative/pkg/apis/duck/v1alpha1
|
github.com/knative/pkg/apis/duck/v1alpha1
|
||||||
github.com/knative/pkg/kmeta
|
github.com/knative/pkg/kmeta
|
||||||
github.com/knative/pkg/ptr
|
|
||||||
github.com/knative/pkg/configmap
|
github.com/knative/pkg/configmap
|
||||||
# github.com/knative/serving v0.6.0
|
# github.com/knative/serving v0.6.0
|
||||||
github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1
|
github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue