Convert remote cluster resources to new cache mechanism (#54502)

Moves tunnel connections and remote clusters to the new
cache collection scheme that was introduced in #52210.
No additional functionality changes have been made here.
This should be a purely mechanical translation to the new
internal caching machinery.
This commit is contained in:
rosstimothy
2025-05-06 15:06:34 +00:00
committed by GitHub
parent b9835b8b57
commit e92f3600a8
6 changed files with 449 additions and 267 deletions
-106
View File
@@ -1784,112 +1784,6 @@ func (c *Cache) processEvent(ctx context.Context, event types.Event) error {
return nil
}
type remoteClustersCacheKey struct {
name string
}
// GetRemoteClusters returns a list of remote clusters
func (c *Cache) GetRemoteClusters(ctx context.Context) ([]types.RemoteCluster, error) {
ctx, span := c.Tracer.Start(ctx, "cache/GetRemoteClusters")
defer span.End()
rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.remoteClusters)
if err != nil {
return nil, trace.Wrap(err)
}
defer rg.Release()
if !rg.IsCacheRead() {
cachedRemotes, err := utils.FnCacheGet(ctx, c.fnCache, remoteClustersCacheKey{}, func(ctx context.Context) ([]types.RemoteCluster, error) {
remotes, err := rg.reader.GetRemoteClusters(ctx)
return remotes, err
})
if err != nil || cachedRemotes == nil {
return nil, trace.Wrap(err)
}
remotes := make([]types.RemoteCluster, 0, len(cachedRemotes))
for _, remote := range cachedRemotes {
remotes = append(remotes, remote.Clone())
}
return remotes, nil
}
return rg.reader.GetRemoteClusters(ctx)
}
// GetRemoteCluster returns a remote cluster by name
func (c *Cache) GetRemoteCluster(ctx context.Context, clusterName string) (types.RemoteCluster, error) {
ctx, span := c.Tracer.Start(ctx, "cache/GetRemoteCluster")
defer span.End()
rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.remoteClusters)
if err != nil {
return nil, trace.Wrap(err)
}
defer rg.Release()
if !rg.IsCacheRead() {
cachedRemote, err := utils.FnCacheGet(ctx, c.fnCache, remoteClustersCacheKey{clusterName}, func(ctx context.Context) (types.RemoteCluster, error) {
remote, err := rg.reader.GetRemoteCluster(ctx, clusterName)
return remote, err
})
if err != nil {
return nil, trace.Wrap(err)
}
return cachedRemote.Clone(), nil
}
rc, err := rg.reader.GetRemoteCluster(ctx, clusterName)
if trace.IsNotFound(err) && rg.IsCacheRead() {
// release read lock early
rg.Release()
// fallback is sane because this method is never used
// in construction of derivative caches.
if rc, err := c.Config.Trust.GetRemoteCluster(ctx, clusterName); err == nil {
return rc, nil
}
}
return rc, trace.Wrap(err)
}
// ListRemoteClusters returns a page of remote clusters.
func (c *Cache) ListRemoteClusters(ctx context.Context, pageSize int, nextToken string) ([]types.RemoteCluster, string, error) {
_, span := c.Tracer.Start(ctx, "cache/ListRemoteClusters")
defer span.End()
rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.remoteClusters)
if err != nil {
return nil, "", trace.Wrap(err)
}
defer rg.Release()
remoteClusters, token, err := rg.reader.ListRemoteClusters(ctx, pageSize, nextToken)
return remoteClusters, token, trace.Wrap(err)
}
// GetTunnelConnections is a part of auth.Cache implementation
func (c *Cache) GetTunnelConnections(clusterName string, opts ...services.MarshalOption) ([]types.TunnelConnection, error) {
_, span := c.Tracer.Start(context.TODO(), "cache/GetTunnelConnections")
defer span.End()
rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.tunnelConnections)
if err != nil {
return nil, trace.Wrap(err)
}
defer rg.Release()
return rg.reader.GetTunnelConnections(clusterName, opts...)
}
// GetAllTunnelConnections is a part of auth.Cache implementation
func (c *Cache) GetAllTunnelConnections(opts ...services.MarshalOption) (conns []types.TunnelConnection, err error) {
_, span := c.Tracer.Start(context.TODO(), "cache/GetAllTunnelConnections")
defer span.End()
rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.tunnelConnections)
if err != nil {
return nil, trace.Wrap(err)
}
defer rg.Release()
return rg.reader.GetAllTunnelConnections(opts...)
}
// ListKubernetesWaitingContainers lists Kubernetes ephemeral
// containers that are waiting to be created until moderated
// session conditions are met.
-64
View File
@@ -1209,70 +1209,6 @@ func TestRecovery(t *testing.T) {
require.Empty(t, cmp.Diff(ca2, out, cmpopts.IgnoreFields(types.Metadata{}, "Revision")))
}
// TestTunnelConnections tests tunnel connections caching
func TestTunnelConnections(t *testing.T) {
t.Parallel()
p := newTestPack(t, ForProxy)
t.Cleanup(p.Close)
clusterName := "example.com"
testResources(t, p, testFuncs[types.TunnelConnection]{
newResource: func(name string) (types.TunnelConnection, error) {
return types.NewTunnelConnection(name, types.TunnelConnectionSpecV2{
ClusterName: clusterName,
ProxyName: "p1",
LastHeartbeat: time.Now().UTC(),
})
},
create: modifyNoContext(p.trustS.UpsertTunnelConnection),
list: func(ctx context.Context) ([]types.TunnelConnection, error) {
return p.trustS.GetTunnelConnections(clusterName)
},
cacheList: func(ctx context.Context) ([]types.TunnelConnection, error) {
return p.cache.GetTunnelConnections(clusterName)
},
update: modifyNoContext(p.trustS.UpsertTunnelConnection),
deleteAll: func(ctx context.Context) error {
return p.trustS.DeleteAllTunnelConnections()
},
})
}
// TestRemoteClusters tests remote clusters caching
func TestRemoteClusters(t *testing.T) {
t.Parallel()
p := newTestPack(t, ForProxy)
t.Cleanup(p.Close)
testResources(t, p, testFuncs[types.RemoteCluster]{
newResource: func(name string) (types.RemoteCluster, error) {
return types.NewRemoteCluster(name)
},
create: func(ctx context.Context, rc types.RemoteCluster) error {
_, err := p.trustS.CreateRemoteCluster(ctx, rc)
return err
},
list: func(ctx context.Context) ([]types.RemoteCluster, error) {
return p.trustS.GetRemoteClusters(ctx)
},
cacheGet: func(ctx context.Context, name string) (types.RemoteCluster, error) {
return p.cache.GetRemoteCluster(ctx, name)
},
cacheList: func(ctx context.Context) ([]types.RemoteCluster, error) {
return p.cache.GetRemoteClusters(ctx)
},
update: func(ctx context.Context, rc types.RemoteCluster) error {
_, err := p.trustS.UpdateRemoteCluster(ctx, rc)
return err
},
deleteAll: func(ctx context.Context) error {
return p.trustS.DeleteAllRemoteClusters(ctx)
},
})
}
func mustCreateDatabase(t *testing.T, name, protocol, uri string) *types.DatabaseV3 {
database, err := types.NewDatabaseV3(
types.Metadata{
+18
View File
@@ -111,6 +111,8 @@ type collections struct {
uiConfigs *collection[types.UIConfig, webUIConfigIndex]
installers *collection[types.Installer, installerIndex]
locks *collection[types.Lock, lockIndex]
tunnelConnections *collection[types.TunnelConnection, tunnelConnectionIndex]
remoteClusters *collection[types.RemoteCluster, remoteClusterIndex]
}
// setupCollections ensures that the appropriate [collection] is
@@ -552,6 +554,22 @@ func setupCollections(c Config) (*collections, error) {
out.locks = collect
out.byKind[resourceKind] = out.locks
case types.KindTunnelConnection:
collect, err := newTunnelConnectionCollection(c.Trust, watch)
if err != nil {
return nil, trace.Wrap(err)
}
out.tunnelConnections = collect
out.byKind[resourceKind] = out.tunnelConnections
case types.KindRemoteCluster:
collect, err := newRemoteClusterCollection(c.Trust, watch)
if err != nil {
return nil, trace.Wrap(err)
}
out.remoteClusters = collect
out.byKind[resourceKind] = out.remoteClusters
}
}
-97
View File
@@ -99,14 +99,12 @@ type legacyCollections struct {
auditQueries collectionReader[services.SecurityAuditQueryGetter]
secReports collectionReader[services.SecurityReportGetter]
secReportsStates collectionReader[services.SecurityReportStateGetter]
tunnelConnections collectionReader[tunnelConnectionGetter]
databaseObjects collectionReader[services.DatabaseObjectsGetter]
discoveryConfigs collectionReader[services.DiscoveryConfigsGetter]
userTasks collectionReader[userTasksGetter]
kubeWaitingContainers collectionReader[kubernetesWaitingContainerGetter]
staticHostUsers collectionReader[staticHostUserGetter]
networkRestrictions collectionReader[networkRestrictionGetter]
remoteClusters collectionReader[remoteClusterGetter]
userLoginStates collectionReader[services.UserLoginStatesGetter]
dynamicWindowsDesktops collectionReader[dynamicWindowsDesktopsGetter]
provisioningStates collectionReader[provisioningStateGetter]
@@ -122,24 +120,6 @@ func setupLegacyCollections(c *Cache, watches []types.WatchKind) (*legacyCollect
for _, watch := range watches {
resourceKind := resourceKindFromWatchKind(watch)
switch watch.Kind {
case types.KindTunnelConnection:
if c.Presence == nil {
return nil, trace.BadParameter("missing parameter Presence")
}
collections.tunnelConnections = &genericCollection[types.TunnelConnection, tunnelConnectionGetter, tunnelConnectionExecutor]{
cache: c,
watch: watch,
}
collections.byKind[resourceKind] = collections.tunnelConnections
case types.KindRemoteCluster:
if c.Presence == nil {
return nil, trace.BadParameter("missing parameter Presence")
}
collections.remoteClusters = &genericCollection[types.RemoteCluster, remoteClusterGetter, remoteClusterExecutor]{
cache: c,
watch: watch,
}
collections.byKind[resourceKind] = collections.remoteClusters
case types.KindAccessRequest:
if c.DynamicAccess == nil {
return nil, trace.BadParameter("missing parameter DynamicAccess")
@@ -344,83 +324,6 @@ func (accessRequestExecutor) getReader(_ *Cache, _ bool) noReader {
var _ executor[types.AccessRequest, noReader] = accessRequestExecutor{}
type tunnelConnectionExecutor struct{}
func (tunnelConnectionExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.TunnelConnection, error) {
return cache.Trust.GetAllTunnelConnections()
}
func (tunnelConnectionExecutor) upsert(ctx context.Context, cache *Cache, resource types.TunnelConnection) error {
return cache.trustCache.UpsertTunnelConnection(resource)
}
func (tunnelConnectionExecutor) deleteAll(ctx context.Context, cache *Cache) error {
return cache.trustCache.DeleteAllTunnelConnections()
}
func (tunnelConnectionExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error {
return cache.trustCache.DeleteTunnelConnection(resource.GetSubKind(), resource.GetName())
}
func (tunnelConnectionExecutor) isSingleton() bool { return false }
func (tunnelConnectionExecutor) getReader(cache *Cache, cacheOK bool) tunnelConnectionGetter {
if cacheOK {
return cache.trustCache
}
return cache.Config.Trust
}
type tunnelConnectionGetter interface {
GetAllTunnelConnections(opts ...services.MarshalOption) (conns []types.TunnelConnection, err error)
GetTunnelConnections(clusterName string, opts ...services.MarshalOption) ([]types.TunnelConnection, error)
}
var _ executor[types.TunnelConnection, tunnelConnectionGetter] = tunnelConnectionExecutor{}
type remoteClusterExecutor struct{}
func (remoteClusterExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.RemoteCluster, error) {
return cache.Trust.GetRemoteClusters(ctx)
}
func (remoteClusterExecutor) upsert(ctx context.Context, cache *Cache, resource types.RemoteCluster) error {
err := cache.trustCache.DeleteRemoteCluster(ctx, resource.GetName())
if err != nil {
if !trace.IsNotFound(err) {
cache.Logger.WarnContext(ctx, "Failed to delete remote cluster", "cluster", resource.GetName(), "error", err)
return trace.Wrap(err)
}
}
_, err = cache.trustCache.CreateRemoteCluster(ctx, resource)
return trace.Wrap(err)
}
func (remoteClusterExecutor) deleteAll(ctx context.Context, cache *Cache) error {
return cache.trustCache.DeleteAllRemoteClusters(ctx)
}
func (remoteClusterExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error {
return cache.trustCache.DeleteRemoteCluster(ctx, resource.GetName())
}
func (remoteClusterExecutor) isSingleton() bool { return false }
func (remoteClusterExecutor) getReader(cache *Cache, cacheOK bool) remoteClusterGetter {
if cacheOK {
return cache.trustCache
}
return cache.Config.Trust
}
type remoteClusterGetter interface {
GetRemoteClusters(ctx context.Context) ([]types.RemoteCluster, error)
GetRemoteCluster(ctx context.Context, clusterName string) (types.RemoteCluster, error)
ListRemoteClusters(ctx context.Context, pageSize int, pageToken string) ([]types.RemoteCluster, string, error)
}
var _ executor[types.RemoteCluster, remoteClusterGetter] = remoteClusterExecutor{}
type userExecutor struct{}
func (userExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.User, error) {
+264
View File
@@ -0,0 +1,264 @@
// Teleport
// Copyright (C) 2025 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cache
import (
"context"
"github.com/gravitational/trace"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/utils/sortcache"
)
type tunnelConnectionIndex string
const tunnelConnectionNameIndex tunnelConnectionIndex = "name"
func newTunnelConnectionCollection(upstream services.Trust, w types.WatchKind) (*collection[types.TunnelConnection, tunnelConnectionIndex], error) {
if upstream == nil {
return nil, trace.BadParameter("missing parameter Trust")
}
return &collection[types.TunnelConnection, tunnelConnectionIndex]{
store: newStore(map[tunnelConnectionIndex]func(types.TunnelConnection) string{
tunnelConnectionNameIndex: func(tc types.TunnelConnection) string {
return tc.GetClusterName() + "/" + tc.GetName()
},
}),
fetcher: func(ctx context.Context, loadSecrets bool) ([]types.TunnelConnection, error) {
out, err := upstream.GetAllTunnelConnections()
return out, trace.Wrap(err)
},
headerTransform: func(hdr *types.ResourceHeader) types.TunnelConnection {
return &types.TunnelConnectionV2{
Kind: hdr.Kind,
Version: hdr.Version,
Metadata: types.Metadata{
Name: hdr.Metadata.Name,
},
Spec: types.TunnelConnectionSpecV2{
ClusterName: hdr.SubKind,
},
}
},
watch: w,
}, nil
}
// GetTunnelConnections is a part of auth.Cache implementation
func (c *Cache) GetTunnelConnections(clusterName string, opts ...services.MarshalOption) ([]types.TunnelConnection, error) {
_, span := c.Tracer.Start(context.TODO(), "cache/GetTunnelConnections")
defer span.End()
rg, err := acquireReadGuard(c, c.collections.tunnelConnections)
if err != nil {
return nil, trace.Wrap(err)
}
defer rg.Release()
if !rg.ReadCache() {
tunnels, err := c.Config.Trust.GetTunnelConnections(clusterName, opts...)
return tunnels, trace.Wrap(err)
}
startKey := clusterName + "/"
endKey := sortcache.NextKey(startKey)
var tunnels []types.TunnelConnection
for t := range rg.store.resources(tunnelConnectionNameIndex, startKey, endKey) {
tunnels = append(tunnels, t.Clone())
}
return tunnels, nil
}
// GetAllTunnelConnections is a part of auth.Cache implementation
func (c *Cache) GetAllTunnelConnections(opts ...services.MarshalOption) (conns []types.TunnelConnection, err error) {
_, span := c.Tracer.Start(context.TODO(), "cache/GetAllTunnelConnections")
defer span.End()
rg, err := acquireReadGuard(c, c.collections.tunnelConnections)
if err != nil {
return nil, trace.Wrap(err)
}
defer rg.Release()
if !rg.ReadCache() {
tunnels, err := c.Config.Trust.GetAllTunnelConnections(opts...)
return tunnels, trace.Wrap(err)
}
tunnels := make([]types.TunnelConnection, 0, rg.store.len())
for t := range rg.store.resources(tunnelConnectionNameIndex, "", "") {
tunnels = append(tunnels, t.Clone())
}
return tunnels, nil
}
type remoteClusterIndex string
const remoteClusterNameIndex remoteClusterIndex = "name"
func newRemoteClusterCollection(upstream services.Trust, w types.WatchKind) (*collection[types.RemoteCluster, remoteClusterIndex], error) {
if upstream == nil {
return nil, trace.BadParameter("missing parameter Trust")
}
return &collection[types.RemoteCluster, remoteClusterIndex]{
store: newStore(map[remoteClusterIndex]func(types.RemoteCluster) string{
remoteClusterNameIndex: types.RemoteCluster.GetName,
}),
fetcher: func(ctx context.Context, loadSecrets bool) ([]types.RemoteCluster, error) {
var out []types.RemoteCluster
var startKey string
for {
clusters, next, err := upstream.ListRemoteClusters(ctx, 0, startKey)
if err != nil {
return nil, trace.Wrap(err)
}
out = append(out, clusters...)
startKey = next
if next == "" {
break
}
}
return out, nil
},
headerTransform: func(hdr *types.ResourceHeader) types.RemoteCluster {
return &types.RemoteClusterV3{
Kind: hdr.Kind,
Version: hdr.Version,
Metadata: types.Metadata{
Name: hdr.Metadata.Name,
},
}
},
watch: w,
}, nil
}
type remoteClustersCacheKey struct {
name string
}
// GetRemoteClusters returns a list of remote clusters
func (c *Cache) GetRemoteClusters(ctx context.Context) ([]types.RemoteCluster, error) {
ctx, span := c.Tracer.Start(ctx, "cache/GetRemoteClusters")
defer span.End()
rg, err := acquireReadGuard(c, c.collections.remoteClusters)
if err != nil {
return nil, trace.Wrap(err)
}
defer rg.Release()
if rg.ReadCache() {
remotes := make([]types.RemoteCluster, 0, rg.store.len())
for rc := range rg.store.resources(remoteClusterNameIndex, "", "") {
remotes = append(remotes, rc.Clone())
}
return remotes, nil
}
cachedRemotes, err := utils.FnCacheGet(ctx, c.fnCache, remoteClustersCacheKey{}, func(ctx context.Context) ([]types.RemoteCluster, error) {
var out []types.RemoteCluster
var startKey string
for {
clusters, next, err := c.Config.Trust.ListRemoteClusters(ctx, 0, startKey)
if err != nil {
return nil, trace.Wrap(err)
}
out = append(out, clusters...)
startKey = next
if next == "" {
break
}
}
return out, nil
})
if err != nil || cachedRemotes == nil {
return nil, trace.Wrap(err)
}
remotes := make([]types.RemoteCluster, 0, len(cachedRemotes))
for _, remote := range cachedRemotes {
remotes = append(remotes, remote.Clone())
}
return remotes, nil
}
// GetRemoteCluster returns a remote cluster by name
func (c *Cache) GetRemoteCluster(ctx context.Context, clusterName string) (types.RemoteCluster, error) {
ctx, span := c.Tracer.Start(ctx, "cache/GetRemoteCluster")
defer span.End()
var upstreamRead bool
getter := genericGetter[types.RemoteCluster, remoteClusterIndex]{
cache: c,
collection: c.collections.remoteClusters,
index: remoteClusterNameIndex,
upstreamGet: func(ctx context.Context, clusterName string) (types.RemoteCluster, error) {
upstreamRead = true
cachedRemote, err := utils.FnCacheGet(ctx, c.fnCache, remoteClustersCacheKey{clusterName}, func(ctx context.Context) (types.RemoteCluster, error) {
remote, err := c.Config.Trust.GetRemoteCluster(ctx, clusterName)
return remote, err
})
if err != nil {
return nil, trace.Wrap(err)
}
return cachedRemote.Clone(), nil
},
clone: types.RemoteCluster.Clone,
}
out, err := getter.get(ctx, clusterName)
if trace.IsNotFound(err) && !upstreamRead {
// fallback is sane because this method is never used
// in construction of derivative caches.
if rc, err := c.Config.Trust.GetRemoteCluster(ctx, clusterName); err == nil {
return rc, nil
}
}
return out, trace.Wrap(err)
}
// ListRemoteClusters returns a page of remote clusters.
func (c *Cache) ListRemoteClusters(ctx context.Context, pageSize int, nextToken string) ([]types.RemoteCluster, string, error) {
_, span := c.Tracer.Start(ctx, "cache/ListRemoteClusters")
defer span.End()
lister := genericLister[types.RemoteCluster, remoteClusterIndex]{
cache: c,
collection: c.collections.remoteClusters,
index: remoteClusterNameIndex,
upstreamList: c.Config.Trust.ListRemoteClusters,
nextToken: types.RemoteCluster.GetName,
clone: types.RemoteCluster.Clone,
}
out, next, err := lister.list(ctx, pageSize, nextToken)
return out, next, trace.Wrap(err)
}
+167
View File
@@ -0,0 +1,167 @@
// Teleport
// Copyright (C) 2025 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cache
import (
"context"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/gravitational/teleport/api/types"
)
// TestRemoteClusters tests remote clusters caching
func TestRemoteClusters(t *testing.T) {
t.Parallel()
t.Run("GetRemoteClusters", func(t *testing.T) {
t.Parallel()
p := newTestPack(t, ForProxy)
t.Cleanup(p.Close)
testResources(t, p, testFuncs[types.RemoteCluster]{
newResource: func(name string) (types.RemoteCluster, error) {
return types.NewRemoteCluster(name)
},
create: func(ctx context.Context, rc types.RemoteCluster) error {
_, err := p.trustS.CreateRemoteCluster(ctx, rc)
return err
},
list: func(ctx context.Context) ([]types.RemoteCluster, error) {
return p.trustS.GetRemoteClusters(ctx)
},
cacheGet: func(ctx context.Context, name string) (types.RemoteCluster, error) {
return p.cache.GetRemoteCluster(ctx, name)
},
cacheList: func(ctx context.Context) ([]types.RemoteCluster, error) {
return p.cache.GetRemoteClusters(ctx)
},
update: func(ctx context.Context, rc types.RemoteCluster) error {
_, err := p.trustS.UpdateRemoteCluster(ctx, rc)
return err
},
deleteAll: func(ctx context.Context) error {
return p.trustS.DeleteAllRemoteClusters(ctx)
},
})
})
t.Run("ListRemoteClusters", func(t *testing.T) {
t.Parallel()
p := newTestPack(t, ForProxy)
t.Cleanup(p.Close)
testResources(t, p, testFuncs[types.RemoteCluster]{
newResource: func(name string) (types.RemoteCluster, error) {
return types.NewRemoteCluster(name)
},
create: func(ctx context.Context, rc types.RemoteCluster) error {
_, err := p.trustS.CreateRemoteCluster(ctx, rc)
return err
},
list: func(ctx context.Context) ([]types.RemoteCluster, error) {
return p.trustS.GetRemoteClusters(ctx)
},
cacheGet: func(ctx context.Context, name string) (types.RemoteCluster, error) {
return p.cache.GetRemoteCluster(ctx, name)
},
cacheList: func(ctx context.Context) ([]types.RemoteCluster, error) {
clusters, _, err := p.cache.ListRemoteClusters(ctx, 0, "")
return clusters, err
},
update: func(ctx context.Context, rc types.RemoteCluster) error {
_, err := p.trustS.UpdateRemoteCluster(ctx, rc)
return err
},
deleteAll: func(ctx context.Context) error {
return p.trustS.DeleteAllRemoteClusters(ctx)
},
})
})
}
// TestTunnelConnections tests tunnel connections caching
func TestTunnelConnections(t *testing.T) {
t.Parallel()
p := newTestPack(t, ForProxy)
t.Cleanup(p.Close)
clusterName := "example.com"
testResources(t, p, testFuncs[types.TunnelConnection]{
newResource: func(name string) (types.TunnelConnection, error) {
return types.NewTunnelConnection(name, types.TunnelConnectionSpecV2{
ClusterName: clusterName,
ProxyName: "p1",
LastHeartbeat: time.Now().UTC(),
})
},
create: modifyNoContext(p.trustS.UpsertTunnelConnection),
list: func(ctx context.Context) ([]types.TunnelConnection, error) {
return p.trustS.GetAllTunnelConnections()
},
cacheList: func(ctx context.Context) ([]types.TunnelConnection, error) {
return p.cache.GetAllTunnelConnections()
},
update: modifyNoContext(p.trustS.UpsertTunnelConnection),
deleteAll: func(ctx context.Context) error {
return p.trustS.DeleteAllTunnelConnections()
},
})
for i := 0; i < 17; i++ {
tunnel, err := types.NewTunnelConnection("conn"+strconv.Itoa(i+1), types.TunnelConnectionSpecV2{
ClusterName: clusterName,
ProxyName: "p1",
LastHeartbeat: time.Now().UTC(),
})
require.NoError(t, err)
require.NoError(t, p.trustS.UpsertTunnelConnection(tunnel))
}
for i := 0; i < 3; i++ {
tunnel, err := types.NewTunnelConnection("conn"+strconv.Itoa(i+100), types.TunnelConnectionSpecV2{
ClusterName: "other-cluster",
ProxyName: "p1",
LastHeartbeat: time.Now().UTC(),
})
require.NoError(t, err)
require.NoError(t, p.trustS.UpsertTunnelConnection(tunnel))
}
require.EventuallyWithT(t, func(tt *assert.CollectT) {
tunnels, err := p.cache.GetAllTunnelConnections()
assert.NoError(tt, err)
assert.Len(tt, tunnels, 20)
}, 15*time.Second, 100*time.Millisecond)
tunnels, err := p.cache.GetTunnelConnections(clusterName)
require.NoError(t, err)
require.Len(t, tunnels, 17)
tunnels, err = p.cache.GetTunnelConnections("other-cluster")
require.NoError(t, err)
require.Len(t, tunnels, 3)
}