Validate service profiles in all namespaces (#2237)

Fixes #2220 

The service profile validation which is part of `linkerd check` only validates service profiles in the Linkerd namespace.  Due to a recent change, service profiles now can exist in any namespace.

Update the logic so that service profiles in all namespaces are validated.  
Additionally:
* Relax validation of service profile names to support external names

Signed-off-by: Alex Leong <alex@buoyant.io>
This commit is contained in:
Alex Leong 2019-02-11 09:52:47 -08:00 committed by GitHub
parent f6e75ec83a
commit 3f333c2860
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 9 additions and 110 deletions

View File

@ -804,14 +804,6 @@ func (hc *HealthChecker) checkCanCreate(namespace, group, version, resource stri
}
func (hc *HealthChecker) validateServiceProfiles() error {
if hc.clientset == nil {
var err error
hc.clientset, err = kubernetes.NewForConfig(hc.kubeAPI.Config)
if err != nil {
return err
}
}
if hc.spClientset == nil {
var err error
hc.spClientset, err = spclient.NewForConfig(hc.kubeAPI.Config)
@ -820,22 +812,12 @@ func (hc *HealthChecker) validateServiceProfiles() error {
}
}
svcProfiles, err := hc.spClientset.LinkerdV1alpha1().ServiceProfiles(hc.ControlPlaneNamespace).List(meta_v1.ListOptions{})
svcProfiles, err := hc.spClientset.LinkerdV1alpha1().ServiceProfiles("").List(meta_v1.ListOptions{})
if err != nil {
return err
}
for _, p := range svcProfiles.Items {
service, namespace, err := profiles.ValidateName(p.Name)
if err != nil {
return err
}
_, err = hc.clientset.CoreV1().Services(namespace).Get(service, meta_v1.GetOptions{})
if err != nil {
return fmt.Errorf("ServiceProfile \"%s\" has unknown service: %s", p.Name, err)
}
// TODO: remove this check once we implement ServiceProfile validation via a
// ValidatingAdmissionWebhook
result := hc.spClientset.RESTClient().Get().RequestURI(p.GetSelfLink()).Do()

View File

@ -6,7 +6,6 @@ import (
"fmt"
"io"
"os"
"strings"
"text/template"
"time"
@ -16,6 +15,7 @@ import (
"github.com/linkerd/linkerd2/pkg/util"
log "github.com/sirupsen/logrus"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation"
"sigs.k8s.io/yaml"
)
@ -51,7 +51,7 @@ var (
minStatus uint32 = 100
maxStatus uint32 = 599
clusterZoneSuffix = []string{"svc", "cluster", "local"}
clusterZoneSuffix = "svc.cluster.local"
errRequestMatchField = errors.New("A request match must have a field set")
errResponseMatchField = errors.New("A response match must have a field set")
@ -332,9 +332,9 @@ func Validate(data []byte) error {
return fmt.Errorf("failed to validate ServiceProfile: %s", err)
}
_, _, err = ValidateName(serviceProfile.Name)
if err != nil {
return err
errs := validation.IsDNS1123Subdomain(serviceProfile.Name)
if len(errs) > 0 {
return fmt.Errorf("ServiceProfile \"%s\" has invalid name: %s", serviceProfile.Name, errs[0])
}
if len(serviceProfile.Spec.Routes) == 0 {
@ -388,22 +388,6 @@ func Validate(data []byte) error {
return nil
}
// ValidateName validates that a ServiceProfile's name is of the form:
// <service>.<namespace>.svc.cluster.local
func ValidateName(name string) (string, string, error) {
nameParts := strings.Split(name, ".")
if len(nameParts) != 2+len(clusterZoneSuffix) {
return "", "", fmt.Errorf("ServiceProfile \"%s\" has invalid name (must be \"<service>.<namespace>.svc.cluster.local\")", name)
}
for i, part := range nameParts[2:] {
if part != clusterZoneSuffix[i] {
return "", "", fmt.Errorf("ServiceProfile \"%s\" has invalid name (must be \"<service>.<namespace>.svc.cluster.local\")", name)
}
}
return nameParts[0], nameParts[1], nil
}
// ValidateRequestMatch validates whether a ServiceProfile RequestMatch has at
// least one field set.
func ValidateRequestMatch(reqMatch *sp.RequestMatch) error {
@ -498,7 +482,7 @@ func buildConfig(namespace, service string) *profileTemplateConfig {
return &profileTemplateConfig{
ServiceNamespace: namespace,
ServiceName: service,
ClusterZone: strings.Join(clusterZoneSuffix, "."),
ClusterZone: clusterZoneSuffix,
}
}

View File

@ -66,25 +66,11 @@ spec:
min: 503`,
},
{
err: errors.New("ServiceProfile \"bad.svc.cluster.local\" has invalid name (must be \"<service>.<namespace>.svc.cluster.local\")"),
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/v1alpha1
kind: ServiceProfile
metadata:
name: bad.svc.cluster.local
namespace: linkerd-ns
spec:
routes:
- name: name-1
condition:
method: GET
pathRegex: /route-1`,
},
{
err: errors.New("ServiceProfile \"name.ns.svc.cluster\" has invalid name (must be \"<service>.<namespace>.svc.cluster.local\")"),
sp: `apiVersion: linkerd.io/v1alpha1
kind: ServiceProfile
metadata:
name: name.ns.svc.cluster
name: ^.^
namespace: linkerd-ns
spec:
routes:
@ -434,56 +420,3 @@ spec:
})
}
}
func TestValidateName(t *testing.T) {
expectations := []struct {
err error
name string
service string
namespace string
}{
{
nil,
"service.ns.svc.cluster.local",
"service",
"ns",
},
{
errors.New("ServiceProfile \"bad.name\" has invalid name (must be \"<service>.<namespace>.svc.cluster.local\")"),
"bad.name",
"",
"",
},
{
errors.New("ServiceProfile \"bad.svc.cluster.local\" has invalid name (must be \"<service>.<namespace>.svc.cluster.local\")"),
"bad.svc.cluster.local",
"",
"",
},
{
errors.New("ServiceProfile \"service.ns.svc.cluster.foo\" has invalid name (must be \"<service>.<namespace>.svc.cluster.local\")"),
"service.ns.svc.cluster.foo",
"",
"",
},
}
for id, exp := range expectations {
t.Run(fmt.Sprintf("%d", id), func(t *testing.T) {
service, namespace, err := ValidateName(exp.name)
if service != exp.service {
t.Fatalf("Unexpected service (Expected: %s, Got: %s)", exp.service, service)
}
if namespace != exp.namespace {
t.Fatalf("Unexpected namespace (Expected: %s, Got: %s)", exp.namespace, namespace)
}
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)
}
}
})
}
}