360 lines
10 KiB
Go
360 lines
10 KiB
Go
// Copyright IBM Corp. 2020, 2026
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package cache
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/boundary/api/aliases"
|
|
"github.com/hashicorp/boundary/api/authtokens"
|
|
"github.com/hashicorp/boundary/api/sessions"
|
|
"github.com/hashicorp/boundary/api/targets"
|
|
cachedb "github.com/hashicorp/boundary/internal/clientcache/internal/db"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNewStatusService(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
t.Run("nil repo", func(t *testing.T) {
|
|
s, err := NewStatusService(ctx, nil)
|
|
assert.Error(t, err)
|
|
assert.Nil(t, s)
|
|
})
|
|
|
|
t.Run("success", func(t *testing.T) {
|
|
s, err := cachedb.Open(ctx)
|
|
require.NoError(t, err)
|
|
r, err := NewRepository(ctx, s, &sync.Map{}, mapBasedAuthTokenKeyringLookup(nil), sliceBasedAuthTokenBoundaryReader(nil))
|
|
require.NoError(t, err)
|
|
|
|
service, err := NewStatusService(ctx, r)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, service)
|
|
})
|
|
}
|
|
|
|
func TestStatus(t *testing.T) {
|
|
ctx := context.Background()
|
|
s, err := cachedb.Open(ctx)
|
|
require.NoError(t, err)
|
|
|
|
boundaryAddr := "address"
|
|
u1 := &user{Id: "u1", Address: boundaryAddr}
|
|
at1a := &authtokens.AuthToken{
|
|
Id: "at_1a",
|
|
Token: "at_1a_token",
|
|
UserId: u1.Id,
|
|
ExpirationTime: time.Now().Add(time.Minute),
|
|
}
|
|
at1b := &authtokens.AuthToken{
|
|
Id: "at_1b",
|
|
Token: "at_1b_token",
|
|
UserId: u1.Id,
|
|
ExpirationTime: time.Now().Add(time.Minute),
|
|
}
|
|
|
|
u2 := &user{Id: "u2", Address: boundaryAddr}
|
|
at2 := &authtokens.AuthToken{
|
|
Id: "at_2",
|
|
Token: "at_2_token",
|
|
UserId: u2.Id,
|
|
ExpirationTime: time.Now().Add(time.Minute),
|
|
}
|
|
|
|
boundaryAuthTokens := []*authtokens.AuthToken{at1a, at1b, at2}
|
|
atMap := map[ringToken]*authtokens.AuthToken{
|
|
{k: "default", t: "default"}: at1a,
|
|
{k: "default2", t: "default2"}: at1b,
|
|
}
|
|
r, err := NewRepository(ctx, s, &sync.Map{}, mapBasedAuthTokenKeyringLookup(atMap), sliceBasedAuthTokenBoundaryReader(boundaryAuthTokens))
|
|
require.NoError(t, err)
|
|
|
|
ss, err := NewStatusService(ctx, r)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("empty status", func(t *testing.T) {
|
|
got, err := ss.Status(ctx)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, &Status{}, got)
|
|
})
|
|
|
|
require.NoError(t, r.AddKeyringToken(ctx, boundaryAddr, KeyringToken{
|
|
KeyringType: "default",
|
|
TokenName: "default",
|
|
AuthTokenId: at1a.Id,
|
|
}))
|
|
require.NoError(t, r.AddKeyringToken(ctx, boundaryAddr, KeyringToken{
|
|
KeyringType: "default2",
|
|
TokenName: "default2",
|
|
AuthTokenId: at1b.Id,
|
|
}))
|
|
require.NoError(t, r.AddRawToken(ctx, boundaryAddr, at1b.Token))
|
|
require.NoError(t, r.AddRawToken(ctx, boundaryAddr, at2.Token))
|
|
|
|
t.Run("users with no resources", func(t *testing.T) {
|
|
got, err := ss.Status(ctx)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, &Status{
|
|
Users: []UserStatus{
|
|
{
|
|
Id: u1.Id,
|
|
BoundaryStatus: BoundaryStatus{
|
|
Address: u1.Address,
|
|
CachingSupported: UnknownCacheSupport,
|
|
},
|
|
AuthTokens: []AuthTokenStatus{
|
|
{
|
|
Id: at1a.Id,
|
|
KeyringReferences: 1,
|
|
KeyringlessReferences: 0,
|
|
},
|
|
{
|
|
Id: at1b.Id,
|
|
KeyringReferences: 1,
|
|
KeyringlessReferences: 1,
|
|
},
|
|
},
|
|
Resources: []ResourceStatus{
|
|
{
|
|
Name: string(resolvableAliasResourceType),
|
|
Count: 0,
|
|
},
|
|
{
|
|
Name: string(targetResourceType),
|
|
Count: 0,
|
|
},
|
|
{
|
|
Name: string(sessionResourceType),
|
|
Count: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Id: u2.Id,
|
|
BoundaryStatus: BoundaryStatus{
|
|
Address: u2.Address,
|
|
CachingSupported: UnknownCacheSupport,
|
|
},
|
|
AuthTokens: []AuthTokenStatus{
|
|
{
|
|
Id: at2.Id,
|
|
KeyringReferences: 0,
|
|
KeyringlessReferences: 1,
|
|
},
|
|
},
|
|
Resources: []ResourceStatus{
|
|
{
|
|
Name: string(resolvableAliasResourceType),
|
|
Count: 0,
|
|
},
|
|
{
|
|
Name: string(targetResourceType),
|
|
Count: 0,
|
|
},
|
|
{
|
|
Name: string(sessionResourceType),
|
|
Count: 0,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, got)
|
|
})
|
|
|
|
t.Run("users with errors", func(t *testing.T) {
|
|
require.NoError(t, r.saveError(ctx, u1, targetResourceType, fmt.Errorf("test error")))
|
|
|
|
got, err := ss.Status(ctx)
|
|
assert.NoError(t, err)
|
|
|
|
lastErr := got.Users[0].Resources[1].LastError
|
|
assert.NotNil(t, lastErr)
|
|
assert.Equal(t, "test error", lastErr.Error)
|
|
assert.NotZero(t, lastErr.LastReturned)
|
|
})
|
|
|
|
t.Run("users with resources", func(t *testing.T) {
|
|
require.NoError(t, r.saveError(ctx, u1, targetResourceType, fmt.Errorf("test error")))
|
|
|
|
ts := []*targets.Target{
|
|
target("1"),
|
|
target("2"),
|
|
target("3"),
|
|
target("4"),
|
|
}
|
|
err = r.refreshTargets(ctx, u1, map[AuthToken]string{{Id: "id"}: "something"},
|
|
WithTargetRetrievalFunc(testTargetStaticResourceRetrievalFunc(testStaticResourceRetrievalFunc(t, [][]*targets.Target{ts}, [][]string{nil}))))
|
|
require.NoError(t, err)
|
|
|
|
err = r.refreshTargets(ctx, u2, map[AuthToken]string{{Id: "id"}: "something"},
|
|
WithTargetRetrievalFunc(testTargetStaticResourceRetrievalFunc(testStaticResourceRetrievalFunc(t, [][]*targets.Target{ts[:2]}, [][]string{nil}))))
|
|
require.NoError(t, err)
|
|
|
|
sess := []*sessions.Session{
|
|
session("1"),
|
|
session("2"),
|
|
session("3"),
|
|
}
|
|
err := r.refreshSessions(ctx, u1, map[AuthToken]string{{Id: "id"}: "something"},
|
|
WithSessionRetrievalFunc(testSessionStaticResourceRetrievalFunc(testStaticResourceRetrievalFunc(t, [][]*sessions.Session{sess}, [][]string{nil}))))
|
|
require.NoError(t, err)
|
|
|
|
als := []*aliases.Alias{
|
|
alias("1"),
|
|
alias("2"),
|
|
alias("3"),
|
|
}
|
|
err = r.refreshResolvableAliases(ctx, u1, map[AuthToken]string{{Id: "id"}: "something"},
|
|
WithAliasRetrievalFunc(testResolvableAliasStaticResourceRetrievalFunc(testStaticResourceRetrievalFuncForId(t, [][]*aliases.Alias{als}, [][]string{nil}))))
|
|
require.NoError(t, err)
|
|
|
|
got, err := ss.Status(ctx)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Len(t, got.Users, 2)
|
|
assert.Equal(t, Map(got.Users, func(i UserStatus) string {
|
|
return i.Id
|
|
}), []string{"u1", "u2"})
|
|
|
|
// User 1 status
|
|
assert.Equal(t, Map(got.Users[0].AuthTokens, func(i AuthTokenStatus) string {
|
|
return i.Id
|
|
}), []string{"at_1a", "at_1b"})
|
|
|
|
assert.Equal(t, Map(got.Users[0].Resources, func(i ResourceStatus) string {
|
|
return i.Name
|
|
}), []string{string(resolvableAliasResourceType), string(targetResourceType), string(sessionResourceType)})
|
|
|
|
assert.Equal(t, Map(got.Users[0].Resources, func(i ResourceStatus) int {
|
|
return i.Count
|
|
}), []int{3, 4, 3})
|
|
|
|
assert.Equal(t, Map(got.Users[0].Resources, func(i ResourceStatus) bool {
|
|
return i.LastError == nil
|
|
}), []bool{true, false, true}, "expected an error for target resource and none for other resources")
|
|
|
|
assert.Equal(t, Map(got.Users[0].Resources, func(i ResourceStatus) bool {
|
|
return i.RefreshToken == nil
|
|
}), []bool{false, false, false})
|
|
|
|
// User 2 status
|
|
assert.Equal(t, Map(got.Users[1].AuthTokens, func(i AuthTokenStatus) string {
|
|
return i.Id
|
|
}), []string{"at_2"})
|
|
|
|
assert.Equal(t, Map(got.Users[1].Resources, func(i ResourceStatus) string {
|
|
return i.Name
|
|
}), []string{string(resolvableAliasResourceType), string(targetResourceType), string(sessionResourceType)})
|
|
|
|
assert.Equal(t, Map(got.Users[1].Resources, func(i ResourceStatus) int {
|
|
return i.Count
|
|
}), []int{0, 2, 0})
|
|
|
|
assert.Equal(t, Map(got.Users[1].Resources, func(i ResourceStatus) bool {
|
|
return i.LastError == nil
|
|
}), []bool{true, true, true})
|
|
|
|
assert.Equal(t, Map(got.Users[1].Resources, func(i ResourceStatus) bool {
|
|
return i.RefreshToken == nil
|
|
}), []bool{true, false, true}, "targets expected to have a refresh token and others aren't")
|
|
})
|
|
}
|
|
|
|
// Map maps a slice of one type into a slice of another using the provided map
|
|
// function
|
|
func Map[T, U any](in []T, f func(T) U) []U {
|
|
var ret []U
|
|
for _, t := range in {
|
|
ret = append(ret, f(t))
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func TestStatus_unsupported(t *testing.T) {
|
|
ctx := context.Background()
|
|
s, err := cachedb.Open(ctx)
|
|
require.NoError(t, err)
|
|
|
|
boundaryAddr := "address"
|
|
u1 := &user{Id: "u1", Address: boundaryAddr}
|
|
at1 := &authtokens.AuthToken{
|
|
Id: "at_1a",
|
|
Token: "at_1a_token",
|
|
UserId: u1.Id,
|
|
ExpirationTime: time.Now().Add(time.Minute),
|
|
}
|
|
|
|
boundaryAuthTokens := []*authtokens.AuthToken{at1}
|
|
atMap := map[ringToken]*authtokens.AuthToken{
|
|
{k: "default", t: "default"}: at1,
|
|
}
|
|
r, err := NewRepository(ctx, s, &sync.Map{}, mapBasedAuthTokenKeyringLookup(atMap), sliceBasedAuthTokenBoundaryReader(boundaryAuthTokens))
|
|
require.NoError(t, err)
|
|
|
|
ss, err := NewStatusService(ctx, r)
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, r.AddKeyringToken(ctx, boundaryAddr, KeyringToken{
|
|
KeyringType: "default",
|
|
TokenName: "default",
|
|
AuthTokenId: at1.Id,
|
|
}))
|
|
|
|
err = r.refreshResolvableAliases(ctx, u1, map[AuthToken]string{{Id: "id"}: "something"},
|
|
WithAliasRetrievalFunc(testResolvableAliasStaticResourceRetrievalFunc(testNoRefreshRetrievalFuncForId[*aliases.Alias](t))))
|
|
require.ErrorIs(t, err, ErrRefreshNotSupported)
|
|
|
|
err = r.refreshTargets(ctx, u1, map[AuthToken]string{{Id: "id"}: "something"},
|
|
WithTargetRetrievalFunc(testTargetStaticResourceRetrievalFunc(testNoRefreshRetrievalFunc[*targets.Target](t))))
|
|
require.ErrorIs(t, err, ErrRefreshNotSupported)
|
|
|
|
err = r.refreshSessions(ctx, u1, map[AuthToken]string{{Id: "id"}: "something"},
|
|
WithSessionRetrievalFunc(testSessionStaticResourceRetrievalFunc(testNoRefreshRetrievalFunc[*sessions.Session](t))))
|
|
require.ErrorIs(t, err, ErrRefreshNotSupported)
|
|
|
|
got, err := ss.Status(ctx)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Len(t, got.Users, 1)
|
|
assert.Greater(t, got.Users[0].BoundaryStatus.LastSupportCheck, time.Duration(0))
|
|
assert.LessOrEqual(t, got.Users[0].BoundaryStatus.LastSupportCheck, time.Second)
|
|
got.Users[0].BoundaryStatus.LastSupportCheck = 0
|
|
|
|
assert.Equal(t, got.Users, []UserStatus{
|
|
{
|
|
Id: u1.Id,
|
|
BoundaryStatus: BoundaryStatus{
|
|
Address: u1.Address,
|
|
CachingSupported: NotSupportedCacheSupport,
|
|
},
|
|
AuthTokens: []AuthTokenStatus{
|
|
{
|
|
Id: at1.Id,
|
|
KeyringReferences: 1,
|
|
},
|
|
},
|
|
Resources: []ResourceStatus{
|
|
{
|
|
Name: string(resolvableAliasResourceType),
|
|
Count: 0,
|
|
},
|
|
{
|
|
Name: string(targetResourceType),
|
|
Count: 0,
|
|
},
|
|
{
|
|
Name: string(sessionResourceType),
|
|
Count: 0,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|