mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
256 lines
8.0 KiB
Go
256 lines
8.0 KiB
Go
package proxy
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/siderolabs/grpc-proxy/proxy"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/metadata"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
type mockMapper struct {
|
|
targets []MachineTarget
|
|
err error
|
|
}
|
|
|
|
func (m *mockMapper) MapMachines(_ context.Context, _ []string) ([]MachineTarget, error) {
|
|
if m.err != nil {
|
|
return nil, m.err
|
|
}
|
|
return m.targets, nil
|
|
}
|
|
|
|
func TestDirector_Director(t *testing.T) {
|
|
d := NewDirector("/tmp/test.sock", 8080, nil)
|
|
t.Cleanup(d.Close)
|
|
|
|
// Use a valid IPv6 address for remote targets.
|
|
remoteTarget := MachineTarget{ID: "id-2", Name: "machine-b", Addr: "fd00::2"}
|
|
localTarget := MachineTarget{ID: "id-1", Name: "machine-a", Addr: "fd00::1"}
|
|
|
|
t.Run("no metadata routes to local", func(t *testing.T) {
|
|
ctx := context.Background()
|
|
mode, backends, err := d.Director(ctx, "/Test/Method")
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, proxy.One2One, mode)
|
|
assert.Len(t, backends, 1)
|
|
assert.IsType(t, (*LocalBackend)(nil), backends[0])
|
|
})
|
|
|
|
t.Run("proxy-authority routes to local", func(t *testing.T) {
|
|
md := metadata.Pairs("proxy-authority", "test", "machines", remoteTarget.Name)
|
|
ctx := metadata.NewIncomingContext(context.Background(), md)
|
|
|
|
mode, backends, err := d.Director(ctx, "/Test/Method")
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, proxy.One2One, mode)
|
|
assert.Len(t, backends, 1)
|
|
assert.IsType(t, (*LocalBackend)(nil), backends[0])
|
|
})
|
|
|
|
t.Run("machine singular local", func(t *testing.T) {
|
|
d.localAddress.Store(localTarget.Addr)
|
|
d.mapper = &mockMapper{targets: []MachineTarget{localTarget}}
|
|
|
|
md := metadata.New(map[string]string{"machine": localTarget.Name})
|
|
ctx := metadata.NewIncomingContext(context.Background(), md)
|
|
|
|
mode, backends, err := d.Director(ctx, "/Test/Method")
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, proxy.One2One, mode)
|
|
assert.Len(t, backends, 1)
|
|
assert.IsType(t, (*LocalBackend)(nil), backends[0])
|
|
})
|
|
|
|
t.Run("machine singular remote", func(t *testing.T) {
|
|
d.localAddress.Store(localTarget.Addr)
|
|
d.mapper = &mockMapper{targets: []MachineTarget{remoteTarget}}
|
|
|
|
md := metadata.New(map[string]string{"machine": remoteTarget.Name})
|
|
ctx := metadata.NewIncomingContext(context.Background(), md)
|
|
|
|
mode, backends, err := d.Director(ctx, "/Test/Method")
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, proxy.One2One, mode)
|
|
assert.Len(t, backends, 1)
|
|
assert.IsType(t, (*RemoteBackend)(nil), backends[0])
|
|
assert.Equal(t, "[fd00::2]:8080", backends[0].(*RemoteBackend).target)
|
|
})
|
|
|
|
t.Run("machine not found", func(t *testing.T) {
|
|
d.mapper = &mockMapper{err: &MachinesNotFoundError{NotFound: []string{"missing"}}}
|
|
|
|
md := metadata.New(map[string]string{"machine": "missing"})
|
|
ctx := metadata.NewIncomingContext(context.Background(), md)
|
|
|
|
_, _, err := d.Director(ctx, "/Test/Method")
|
|
|
|
require.Error(t, err)
|
|
st, ok := status.FromError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, codes.InvalidArgument, st.Code())
|
|
assert.Contains(t, st.Message(), "machine not found: missing")
|
|
})
|
|
|
|
t.Run("machines plural single local", func(t *testing.T) {
|
|
d.localAddress.Store(localTarget.Addr)
|
|
d.mapper = &mockMapper{targets: []MachineTarget{localTarget}}
|
|
|
|
md := metadata.Pairs("machines", localTarget.Name)
|
|
ctx := metadata.NewIncomingContext(context.Background(), md)
|
|
|
|
mode, backends, err := d.Director(ctx, "/Test/Method")
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, proxy.One2Many, mode)
|
|
assert.Len(t, backends, 1)
|
|
|
|
mb := backends[0].(*MetadataBackend)
|
|
assert.Equal(t, localTarget.ID, mb.MachineID)
|
|
assert.Equal(t, localTarget.Name, mb.MachineName)
|
|
assert.Equal(t, localTarget.Addr, mb.MachineAddr)
|
|
assert.IsType(t, (*LocalBackend)(nil), mb.Backend)
|
|
})
|
|
|
|
t.Run("machines plural multiple", func(t *testing.T) {
|
|
d.localAddress.Store(localTarget.Addr)
|
|
d.mapper = &mockMapper{targets: []MachineTarget{localTarget, remoteTarget}}
|
|
|
|
md := metadata.Pairs("machines", localTarget.Name, "machines", remoteTarget.Name)
|
|
ctx := metadata.NewIncomingContext(context.Background(), md)
|
|
|
|
mode, backends, err := d.Director(ctx, "/Test/Method")
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, proxy.One2Many, mode)
|
|
assert.Len(t, backends, 2)
|
|
|
|
// First backend should be local.
|
|
mb0 := backends[0].(*MetadataBackend)
|
|
assert.Equal(t, localTarget.ID, mb0.MachineID)
|
|
assert.IsType(t, (*LocalBackend)(nil), mb0.Backend)
|
|
|
|
// Second backend should be remote.
|
|
mb1 := backends[1].(*MetadataBackend)
|
|
assert.Equal(t, remoteTarget.ID, mb1.MachineID)
|
|
assert.IsType(t, (*RemoteBackend)(nil), mb1.Backend)
|
|
assert.Equal(t, "[fd00::2]:8080", mb1.Backend.(*RemoteBackend).target)
|
|
})
|
|
|
|
t.Run("machines empty string", func(t *testing.T) {
|
|
d.mapper = &mockMapper{err: &MachinesNotFoundError{NotFound: []string{""}}}
|
|
|
|
md := metadata.Pairs("machines", "")
|
|
ctx := metadata.NewIncomingContext(context.Background(), md)
|
|
|
|
_, _, err := d.Director(ctx, "/Test/Method")
|
|
|
|
require.Error(t, err)
|
|
st, ok := status.FromError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, codes.InvalidArgument, st.Code())
|
|
assert.Contains(t, st.Message(), "machine not found")
|
|
})
|
|
|
|
t.Run("machine empty slice", func(t *testing.T) {
|
|
md := metadata.MD{"machine": []string{}}
|
|
ctx := metadata.NewIncomingContext(context.Background(), md)
|
|
|
|
_, _, err := d.Director(ctx, "/Test/Method")
|
|
|
|
require.Error(t, err)
|
|
st, ok := status.FromError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, codes.InvalidArgument, st.Code())
|
|
assert.Contains(t, st.Message(), "proxy metadata 'machine' must have exactly one value")
|
|
})
|
|
|
|
t.Run("machines empty slice", func(t *testing.T) {
|
|
md := metadata.MD{"machines": []string{}}
|
|
ctx := metadata.NewIncomingContext(context.Background(), md)
|
|
|
|
_, _, err := d.Director(ctx, "/Test/Method")
|
|
|
|
require.Error(t, err)
|
|
st, ok := status.FromError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, codes.InvalidArgument, st.Code())
|
|
assert.Contains(t, st.Message(), "proxy metadata 'machines' is empty")
|
|
})
|
|
|
|
t.Run("both machine and machines set", func(t *testing.T) {
|
|
md := metadata.Pairs("machine", "m1", "machines", "m1", "machines", "m2")
|
|
ctx := metadata.NewIncomingContext(context.Background(), md)
|
|
|
|
_, _, err := d.Director(ctx, "/Test/Method")
|
|
|
|
require.Error(t, err)
|
|
st, ok := status.FromError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, codes.InvalidArgument, st.Code())
|
|
assert.Contains(t, st.Message(), "both 'machine' and 'machines' proxy metadata are set")
|
|
})
|
|
|
|
t.Run("machines not found", func(t *testing.T) {
|
|
d.mapper = &mockMapper{err: &MachinesNotFoundError{NotFound: []string{"missing"}}}
|
|
|
|
md := metadata.Pairs("machines", "missing")
|
|
ctx := metadata.NewIncomingContext(context.Background(), md)
|
|
|
|
_, _, err := d.Director(ctx, "/Test/Method")
|
|
|
|
require.Error(t, err)
|
|
st, ok := status.FromError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, codes.InvalidArgument, st.Code())
|
|
})
|
|
|
|
t.Run("machines mapper generic error", func(t *testing.T) {
|
|
d.mapper = &mockMapper{err: errors.New("boom")}
|
|
|
|
md := metadata.Pairs("machines", "any")
|
|
ctx := metadata.NewIncomingContext(context.Background(), md)
|
|
|
|
_, _, err := d.Director(ctx, "/Test/Method")
|
|
|
|
require.Error(t, err)
|
|
st, ok := status.FromError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, codes.Internal, st.Code())
|
|
})
|
|
}
|
|
|
|
func TestMapErrorToStatus(t *testing.T) {
|
|
t.Run("machines not found", func(t *testing.T) {
|
|
err := mapErrorToStatus(&MachinesNotFoundError{NotFound: []string{"a", "b"}})
|
|
st, ok := status.FromError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, codes.InvalidArgument, st.Code())
|
|
})
|
|
|
|
t.Run("already grpc status", func(t *testing.T) {
|
|
original := status.Error(codes.DeadlineExceeded, "timeout")
|
|
err := mapErrorToStatus(original)
|
|
st, ok := status.FromError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, codes.DeadlineExceeded, st.Code())
|
|
})
|
|
|
|
t.Run("generic error", func(t *testing.T) {
|
|
err := mapErrorToStatus(errors.New("something broke"))
|
|
st, ok := status.FromError(err)
|
|
require.True(t, ok)
|
|
assert.Equal(t, codes.Internal, st.Code())
|
|
assert.Contains(t, st.Message(), "something broke")
|
|
})
|
|
}
|