linkerd2/controller/api/util/api_utils_test.go

340 lines
9.7 KiB
Go

package util
import (
"errors"
"reflect"
"testing"
pb "github.com/linkerd/linkerd2/controller/gen/public"
"github.com/linkerd/linkerd2/pkg/k8s"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
k8sError "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestGRPCError(t *testing.T) {
t.Run("Maps errors to gRPC errors", func(t *testing.T) {
expectations := map[error]error{
nil: nil,
errors.New("normal erro"): errors.New("rpc error: code = Unknown desc = normal erro"),
status.Error(codes.NotFound, "grpc not found"): errors.New("rpc error: code = NotFound desc = grpc not found"),
k8sError.NewNotFound(schema.GroupResource{Group: "foo", Resource: "bar"}, "http not found"): errors.New("rpc error: code = NotFound desc = bar.foo \"http not found\" not found"),
k8sError.NewServiceUnavailable("unavailable"): errors.New("rpc error: code = Unavailable desc = unavailable"),
k8sError.NewGone("gone"): errors.New("rpc error: code = Internal desc = gone"),
}
for in, out := range expectations {
err := GRPCError(in)
if err != nil || out != nil {
if (err == nil && out != nil) ||
(err != nil && out == nil) ||
(err.Error() != out.Error()) {
t.Fatalf("Expected GRPCError to return [%s], got: [%s]", out, GRPCError(in))
}
}
}
})
}
func TestBuildStatSummaryRequest(t *testing.T) {
t.Run("Maps Kubernetes friendly names to canonical names", func(t *testing.T) {
expectations := map[string]string{
"deployments": k8s.Deployment,
"deployment": k8s.Deployment,
"deploy": k8s.Deployment,
"pods": k8s.Pod,
"pod": k8s.Pod,
"po": k8s.Pod,
}
for friendly, canonical := range expectations {
statSummaryRequest, err := BuildStatSummaryRequest(
StatsRequestParams{
ResourceType: friendly,
},
)
if err != nil {
t.Fatalf("Unexpected error from BuildStatSummaryRequest [%s => %s]: %s", friendly, canonical, err)
}
if statSummaryRequest.Selector.Resource.Type != canonical {
t.Fatalf("Unexpected resource type from BuildStatSummaryRequest [%s => %s]: %s", friendly, canonical, statSummaryRequest.Selector.Resource.Type)
}
}
})
t.Run("Parses valid time windows", func(t *testing.T) {
expectations := []string{
"1m",
"60s",
"1m",
}
for _, timeWindow := range expectations {
statSummaryRequest, err := BuildStatSummaryRequest(
StatsRequestParams{
TimeWindow: timeWindow,
ResourceType: k8s.Deployment,
},
)
if err != nil {
t.Fatalf("Unexpected error from BuildStatSummaryRequest [%s => %s]", timeWindow, err)
}
if statSummaryRequest.TimeWindow != timeWindow {
t.Fatalf("Unexpected TimeWindow from BuildStatSummaryRequest [%s => %s]", timeWindow, statSummaryRequest.TimeWindow)
}
}
})
t.Run("Rejects invalid time windows", func(t *testing.T) {
expectations := map[string]string{
"1": "time: missing unit in duration 1",
"s": "time: invalid duration s",
}
for timeWindow, msg := range expectations {
_, err := BuildStatSummaryRequest(
StatsRequestParams{
TimeWindow: timeWindow,
},
)
if err == nil {
t.Fatalf("BuildStatSummaryRequest(%s) unexpectedly succeeded, should have returned %s", timeWindow, msg)
}
if err.Error() != msg {
t.Fatalf("BuildStatSummaryRequest(%s) should have returned: %s but got unexpected message: %s", timeWindow, msg, err)
}
}
})
t.Run("Rejects invalid Kubernetes resource types", func(t *testing.T) {
expectations := map[string]string{
"foo": "cannot find Kubernetes canonical name from friendly name [foo]",
"": "cannot find Kubernetes canonical name from friendly name []",
}
for input, msg := range expectations {
_, err := BuildStatSummaryRequest(
StatsRequestParams{
ResourceType: input,
},
)
if err == nil {
t.Fatalf("BuildStatSummaryRequest(%s) unexpectedly succeeded, should have returned %s", input, msg)
}
if err.Error() != msg {
t.Fatalf("BuildStatSummaryRequest(%s) should have returned: %s but got unexpected message: %s", input, msg, err)
}
}
})
}
func TestBuildTopRoutesRequest(t *testing.T) {
t.Run("Parses valid time windows", func(t *testing.T) {
expectations := []string{
"1m",
"60s",
"1m",
}
for _, timeWindow := range expectations {
topRoutesRequest, err := BuildTopRoutesRequest(
StatsRequestParams{
TimeWindow: timeWindow,
ResourceType: k8s.Service,
},
)
if err != nil {
t.Fatalf("Unexpected error from BuildTopRoutesRequest [%s => %s]", timeWindow, err)
}
if topRoutesRequest.TimeWindow != timeWindow {
t.Fatalf("Unexpected TimeWindow from BuildTopRoutesRequest [%s => %s]", timeWindow, topRoutesRequest.TimeWindow)
}
}
})
t.Run("Rejects invalid time windows", func(t *testing.T) {
expectations := map[string]string{
"1": "time: missing unit in duration 1",
"s": "time: invalid duration s",
}
for timeWindow, msg := range expectations {
_, err := BuildTopRoutesRequest(
StatsRequestParams{
TimeWindow: timeWindow,
ResourceType: k8s.Service,
},
)
if err == nil {
t.Fatalf("BuildTopRoutesRequest(%s) unexpectedly succeeded, should have returned %s", timeWindow, msg)
}
if err.Error() != msg {
t.Fatalf("BuildTopRoutesRequest(%s) should have returned: %s but got unexpected message: %s", timeWindow, msg, err)
}
}
})
t.Run("Rejects non-service Kubernetes resource types", func(t *testing.T) {
resourceTypes := []string{
"deployment",
"pod",
"namespace",
}
msg := "routes request must target a service"
for _, input := range resourceTypes {
_, err := BuildTopRoutesRequest(
StatsRequestParams{
ResourceType: input,
},
)
if err == nil {
t.Fatalf("BuildTopRoutesRequest(%s) unexpectedly succeeded, should have returned %s", input, msg)
}
if err.Error() != msg {
t.Fatalf("BuildTopRoutesRequest(%s) should have returned: %s but got unexpected message: %s", input, msg, err)
}
}
})
t.Run("Rejects all-namespaces flag", func(t *testing.T) {
msg := "all namespaces is not supported for routes request"
_, err := BuildTopRoutesRequest(
StatsRequestParams{
ResourceType: k8s.Service,
AllNamespaces: true,
},
)
if err == nil {
t.Fatalf("BuildTopRoutesRequest unexpectedly succeeded, should have returned %s", msg)
}
if err.Error() != msg {
t.Fatalf("BuildTopRoutesRequest should have returned: %s but got unexpected message: %s", msg, err)
}
})
}
func TestBuildResource(t *testing.T) {
type resourceExp struct {
namespace string
args []string
resource pb.Resource
}
t.Run("Rejects duped resources", func(t *testing.T) {
msg := "cannot supply duplicate resources"
expectations := []resourceExp{
resourceExp{
namespace: "test-ns",
args: []string{"foo", "foo"},
},
resourceExp{
namespace: "test-ns",
args: []string{"all", "all"},
},
}
for _, exp := range expectations {
_, err := BuildResources(exp.namespace, exp.args)
if err == nil {
t.Fatalf("BuildResource called with duped resources unexpectedly succeeded, should have returned %s", msg)
}
if err.Error() != msg {
t.Fatalf("BuildResource called with duped resources should have returned: %s but got unexpected message: %s", msg, err)
}
}
})
t.Run("Ensures 'all' can't be supplied alongside other resources", func(t *testing.T) {
msg := "'all' can't be supplied alongside other resources"
expectations := []resourceExp{
resourceExp{
namespace: "test-ns",
args: []string{"po", "foo", "all"},
},
resourceExp{
namespace: "test-ns",
args: []string{"foo", "all"},
},
resourceExp{
namespace: "test-ns",
args: []string{"all", "foo"},
},
}
for _, exp := range expectations {
_, err := BuildResources(exp.namespace, exp.args)
if err == nil {
t.Fatalf("BuildResource called with 'all' and another resource unexpectedly succeeded, should have returned %s", msg)
}
if err.Error() != msg {
t.Fatalf("BuildResource called with 'all' and another resource should have returned: %s but got unexpected message: %s", msg, err)
}
}
})
t.Run("Correctly parses Kubernetes resources from the command line", func(t *testing.T) {
expectations := []resourceExp{
resourceExp{
namespace: "test-ns",
args: []string{"deployments"},
resource: pb.Resource{
Namespace: "test-ns",
Type: k8s.Deployment,
Name: "",
},
},
resourceExp{
namespace: "",
args: []string{"deploy/foo"},
resource: pb.Resource{
Namespace: "",
Type: k8s.Deployment,
Name: "foo",
},
},
resourceExp{
namespace: "foo-ns",
args: []string{"po", "foo"},
resource: pb.Resource{
Namespace: "foo-ns",
Type: k8s.Pod,
Name: "foo",
},
},
resourceExp{
namespace: "foo-ns",
args: []string{"ns", "foo-ns2"},
resource: pb.Resource{
Namespace: "",
Type: k8s.Namespace,
Name: "foo-ns2",
},
},
resourceExp{
namespace: "foo-ns",
args: []string{"ns/foo-ns2"},
resource: pb.Resource{
Namespace: "",
Type: k8s.Namespace,
Name: "foo-ns2",
},
},
}
for _, exp := range expectations {
res, err := BuildResources(exp.namespace, exp.args)
if err != nil {
t.Fatalf("Unexpected error from BuildResources(%+v) => %s", exp, err)
}
if !reflect.DeepEqual(exp.resource, res[0]) {
t.Fatalf("Expected resource to be [%+v] but was [%+v]", exp.resource, res[0])
}
}
})
}