diff --git a/pkg/service/configcache/service.go b/pkg/service/configcache/service.go index a58e42004..00af6664d 100644 --- a/pkg/service/configcache/service.go +++ b/pkg/service/configcache/service.go @@ -22,6 +22,9 @@ type ConfigCacher interface { // or ErrNoHostConfig if config of the particular host doesn't exist. Read(clusterID uuid.UUID, host string) (NodeConfig, error) + // AvailableHosts returns list of hosts of given cluster that keep their configuration in cache. + AvailableHosts(ctx context.Context, clusterID uuid.UUID) ([]string, error) + // ForceUpdateCluster updates single cluster config in cache and does it outside the background process. ForceUpdateCluster(ctx context.Context, clusterID uuid.UUID) bool @@ -123,6 +126,29 @@ func (svc *Service) ForceUpdateCluster(ctx context.Context, clusterID uuid.UUID) return svc.updateSingle(ctx, c) } +// AvailableHosts returns list of hosts of given cluster that keep their configuration in cache. +func (svc *Service) AvailableHosts(ctx context.Context, clusterID uuid.UUID) ([]string, error) { + logger := svc.logger.Named("Listing available hosts").With("cluster", clusterID) + + clusterConfig, err := svc.readClusterConfig(clusterID) + if err != nil { + return nil, err + } + + var availableHosts []string + clusterConfig.Range(func(key, value any) bool { + host, ok := key.(string) + if !ok { + logger.Error(ctx, "Cannot cast to string", "host", key, "error", err) + return false + } + availableHosts = append(availableHosts, host) + return true + }) + + return availableHosts, nil +} + func (svc *Service) updateSingle(ctx context.Context, c *cluster.Cluster) bool { logger := svc.logger.Named("Cluster config update").With("cluster", c.ID) diff --git a/pkg/service/configcache/service_test.go b/pkg/service/configcache/service_test.go index ee7cf858f..361c7545d 100644 --- a/pkg/service/configcache/service_test.go +++ b/pkg/service/configcache/service_test.go @@ -107,6 +107,76 @@ func TestService_Read(t *testing.T) { } } +func TestService_AvailableHosts(t *testing.T) { + host1NodeConfig := NodeConfig{ + NodeInfo: &scyllaclient.NodeInfo{ + AgentVersion: "expectedVersion", + }, + } + + cluster1UUID := uuid.MustRandom() + host1ID := "host1" + initialState := convertMapToSyncMap( + map[any]any{ + cluster1UUID.String(): convertMapToSyncMap( + map[any]any{ + host1ID: host1NodeConfig, + }, + ), + }, + ) + + for _, tc := range []struct { + name string + cluster uuid.UUID + state *sync.Map + expectedError error + expectedResult []string + }{ + { + name: "get all available hosts", + cluster: cluster1UUID, + state: initialState, + expectedError: nil, + expectedResult: []string{host1ID}, + }, + { + name: "get all available hosts of non-existing cluster", + cluster: uuid.MustRandom(), + state: initialState, + expectedError: ErrNoClusterConfig, + expectedResult: nil, + }, + } { + t.Run(tc.name, func(t *testing.T) { + // Given + svc := Service{ + svcConfig: DefaultConfig(), + clusterSvc: &mockClusterServicer{}, + scyllaClient: mockProviderFunc, + secretsStore: &mockStore{}, + configs: tc.state, + } + + // When + hosts, err := svc.AvailableHosts(context.Background(), tc.cluster) + if err != tc.expectedError { + t.Fatalf("expected {%v}, but got {%v}", tc.expectedError, err) + } + + // Then + if len(hosts) != len(tc.expectedResult) { + t.Fatalf("expected hosts size = {%v}, but got {%v}", len(tc.expectedResult), len(hosts)) + } + for i := 0; i < len(tc.expectedResult); i++ { + if hosts[i] != tc.expectedResult[i] { + t.Fatalf("expected host {%v}, but got {%v}", host1ID, hosts[0]) + } + } + }) + } +} + func TestService_Run(t *testing.T) { t.Run("validate context cancellation handling", func(t *testing.T) { svc := Service{