mirror of https://github.com/linkerd/linkerd2.git
424 lines
9.9 KiB
Go
424 lines
9.9 KiB
Go
package profiles
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
)
|
|
|
|
type spExp struct {
|
|
err error
|
|
sp string
|
|
}
|
|
|
|
func TestValidate(t *testing.T) {
|
|
expectations := []spExp{
|
|
{
|
|
err: nil,
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
routes:
|
|
- name: name-1
|
|
condition:
|
|
method: GET
|
|
pathRegex: /route-1`,
|
|
},
|
|
{
|
|
err: nil,
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
retryBudget:
|
|
minRetriesPerSecond: 5
|
|
retryRatio: 0.2
|
|
ttl: 10ms
|
|
routes:
|
|
- name: name-1
|
|
condition:
|
|
method: GET
|
|
pathRegex: /route-1
|
|
any:
|
|
- all:
|
|
- method: POST
|
|
- pathRegex: '/authors/\d+'
|
|
- all:
|
|
- not:
|
|
method: DELETE
|
|
- pathRegex: /info.txt
|
|
responseClasses:
|
|
- condition:
|
|
status:
|
|
min: 500
|
|
max: 599
|
|
all:
|
|
- status:
|
|
min: 500
|
|
max: 599
|
|
- not:
|
|
status:
|
|
min: 503`,
|
|
},
|
|
{
|
|
err: errors.New("ServiceProfile \"^.^\" has invalid name: a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: ^.^
|
|
namespace: linkerd-ns
|
|
spec:
|
|
routes:
|
|
- name: name-1
|
|
condition:
|
|
method: GET
|
|
pathRegex: /route-1`,
|
|
},
|
|
{
|
|
err: errors.New("failed to validate ServiceProfile: error unmarshaling JSON: while decoding JSON: json: unknown field \"foo\""),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
foo: bar
|
|
routes:
|
|
- name: name-1
|
|
condition:
|
|
method: GET
|
|
pathRegex: /route-1`,
|
|
},
|
|
{
|
|
err: errors.New("failed to validate ServiceProfile: error unmarshaling JSON: while decoding JSON: json: unknown field \"foo\""),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
routes:
|
|
- name: name-1
|
|
foo: bar
|
|
condition:
|
|
method: GET
|
|
pathRegex: /route-1`,
|
|
},
|
|
{
|
|
err: errors.New("failed to validate ServiceProfile: error unmarshaling JSON: while decoding JSON: json: unknown field \"foo\""),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
routes:
|
|
- name: name-1
|
|
condition:
|
|
foo: bar
|
|
method: GET
|
|
pathRegex: /route-1`,
|
|
},
|
|
{
|
|
err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has no routes"),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:`,
|
|
},
|
|
{
|
|
err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has a route with no condition"),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
routes:
|
|
- name: name-1`,
|
|
},
|
|
{
|
|
err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has a route with no name"),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
routes:
|
|
- condition:
|
|
method: GET
|
|
pathRegex: /route-1`,
|
|
},
|
|
{
|
|
err: errors.New("failed to validate ServiceProfile: error unmarshaling JSON: while decoding JSON: json: unknown field \"foo\""),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
routes:
|
|
- name: name-1
|
|
condition:
|
|
foo: bar
|
|
method: GET
|
|
pathRegex: /route-1
|
|
not:
|
|
method: GET`,
|
|
},
|
|
{
|
|
err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has a route with no condition"),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
routes:
|
|
- name: name-1
|
|
condition:`,
|
|
},
|
|
{
|
|
err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has a route with an invalid condition: A request match must have a field set"),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
routes:
|
|
- name: name-1
|
|
condition:
|
|
method:`,
|
|
},
|
|
{
|
|
err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has a response class with no condition"),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
routes:
|
|
- name: name-1
|
|
condition:
|
|
method: GET
|
|
pathRegex: /route-1
|
|
responseClasses:
|
|
- condition:`,
|
|
},
|
|
{
|
|
err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has a response class with an invalid condition: A response match must have a field set"),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
routes:
|
|
- name: name-1
|
|
condition:
|
|
method: GET
|
|
pathRegex: /route-1
|
|
responseClasses:
|
|
- condition:
|
|
status:
|
|
min: 500
|
|
max: 599
|
|
all:
|
|
- status:`,
|
|
},
|
|
{
|
|
err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has a response class with an invalid condition: Range maximum must be between 100 and 599, inclusive"),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
routes:
|
|
- name: name-1
|
|
condition:
|
|
method: GET
|
|
pathRegex: /route-1
|
|
responseClasses:
|
|
- condition:
|
|
status:
|
|
min: 500
|
|
max: 600`,
|
|
},
|
|
{
|
|
err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has a response class with an invalid condition: Range maximum cannot be smaller than minimum"),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
routes:
|
|
- name: name-1
|
|
condition:
|
|
method: GET
|
|
pathRegex: /route-1
|
|
responseClasses:
|
|
- condition:
|
|
status:
|
|
min: 500
|
|
max: 599
|
|
all:
|
|
- status:
|
|
min: 500
|
|
max: 599
|
|
- not:
|
|
status:
|
|
min: 300
|
|
max: 200`,
|
|
},
|
|
{
|
|
err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has a response class with an invalid condition: Range minimum must be between 100 and 599, inclusive"),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
routes:
|
|
- name: name-1
|
|
condition:
|
|
method: GET
|
|
pathRegex: /route-1
|
|
responseClasses:
|
|
- condition:
|
|
status:
|
|
min: 500
|
|
max: 599
|
|
all:
|
|
- status:
|
|
min: 500
|
|
max: 599
|
|
- not:
|
|
status:
|
|
min: 1`,
|
|
},
|
|
{
|
|
err: errors.New("failed to validate ServiceProfile: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal bool into Go struct field Range.spec.routes.responseClasses.condition.all.not.status.min of type uint32"),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
routes:
|
|
- name: name-1
|
|
condition:
|
|
method: GET
|
|
pathRegex: /route-1
|
|
responseClasses:
|
|
- condition:
|
|
status:
|
|
min: 500
|
|
max: 599
|
|
all:
|
|
- status:
|
|
min: 500
|
|
max: 599
|
|
- not:
|
|
status:
|
|
min: false`,
|
|
},
|
|
{
|
|
err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" RetryBudget missing TTL field"),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
retryBudget:
|
|
minRetriesPerSecond: 5
|
|
retryRatio: 0.2
|
|
routes:
|
|
- name: name-1
|
|
condition:
|
|
method: GET
|
|
pathRegex: /route-1`,
|
|
},
|
|
{
|
|
err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" RetryBudget: time: invalid duration foo"),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
retryBudget:
|
|
minRetriesPerSecond: 5
|
|
retryRatio: 0.2
|
|
ttl: foo
|
|
routes:
|
|
- name: name-1
|
|
condition:
|
|
method: GET
|
|
pathRegex: /route-1`,
|
|
},
|
|
{
|
|
err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" RetryBudget RetryRatio must be non-negative: -0.200000"),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
retryBudget:
|
|
minRetriesPerSecond: 5
|
|
retryRatio: -0.2
|
|
ttl: 10s
|
|
routes:
|
|
- name: name-1
|
|
condition:
|
|
method: GET
|
|
pathRegex: /route-1`,
|
|
},
|
|
{
|
|
err: errors.New("failed to validate ServiceProfile: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal number -5 into Go struct field RetryBudget.spec.retryBudget.minRetriesPerSecond of type uint32"),
|
|
sp: `apiVersion: linkerd.io/v1alpha2
|
|
kind: ServiceProfile
|
|
metadata:
|
|
name: name.ns.svc.cluster.local
|
|
namespace: linkerd-ns
|
|
spec:
|
|
retryBudget:
|
|
minRetriesPerSecond: -5
|
|
retryRatio: 0.2
|
|
ttl: 10s
|
|
routes:
|
|
- name: name-1
|
|
condition:
|
|
method: GET
|
|
pathRegex: /route-1`,
|
|
},
|
|
}
|
|
|
|
for id, exp := range expectations {
|
|
exp := exp // pin
|
|
t.Run(fmt.Sprintf("%d", id), func(t *testing.T) {
|
|
err := Validate([]byte(exp.sp))
|
|
if err != nil || exp.err != nil {
|
|
if (err == nil && exp.err != nil) ||
|
|
(err != nil && exp.err == nil) ||
|
|
(err.Error() != exp.err.Error()) {
|
|
t.Fatalf("Unexpected error (Expected: %s, Got: %s)", exp.err, err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|