diff --git a/cmd/all-in-one/main.go b/cmd/all-in-one/main.go index b393a6c962d..502efe2c895 100644 --- a/cmd/all-in-one/main.go +++ b/cmd/all-in-one/main.go @@ -171,8 +171,8 @@ by default uses only in-memory database.`, queryTelset := baseTelset // copy queryTelset.Metrics = queryMetricsFactory querySrv := startQuery( - svc, qOpts, qOpts.BuildQueryServiceOptions(storageFactory, logger), - qOpts.BuildQueryServiceOptionsV2(storageFactory, logger), + svc, qOpts, qOpts.BuildQueryServiceOptions(storageFactory.InitArchiveStorage, logger), + qOpts.BuildQueryServiceOptionsV2(storageFactory.InitArchiveStorage, logger), traceReader, dependencyReader, metricsQueryService, tm, queryTelset, ) diff --git a/cmd/jaeger/config-remote-storage.yaml b/cmd/jaeger/config-remote-storage.yaml index 655703b2034..966812aaffb 100644 --- a/cmd/jaeger/config-remote-storage.yaml +++ b/cmd/jaeger/config-remote-storage.yaml @@ -24,6 +24,7 @@ extensions: jaeger_query: storage: traces: some-storage + traces_archive: another-storage ui: config_file: ./cmd/jaeger/config-ui.json @@ -34,6 +35,11 @@ extensions: endpoint: localhost:17271 tls: insecure: true + another-storage: + grpc: + endpoint: localhost:17272 + tls: + insecure: true receivers: otlp: diff --git a/cmd/jaeger/internal/extension/jaegerstorage/config.go b/cmd/jaeger/internal/extension/jaegerstorage/config.go index 460e5cbc78f..0612a805019 100644 --- a/cmd/jaeger/internal/extension/jaegerstorage/config.go +++ b/cmd/jaeger/internal/extension/jaegerstorage/config.go @@ -74,7 +74,7 @@ func (cfg *TraceBackend) Unmarshal(conf *confmap.Conf) error { } if conf.IsSet("cassandra") { cfg.Cassandra = &cassandra.Options{ - Primary: cassandra.NamespaceConfig{ + NamespaceConfig: cassandra.NamespaceConfig{ Configuration: casCfg.DefaultConfiguration(), Enabled: true, }, diff --git a/cmd/jaeger/internal/extension/jaegerstorage/config_test.go b/cmd/jaeger/internal/extension/jaegerstorage/config_test.go index 9f531caf255..c8e02148a7a 100644 --- a/cmd/jaeger/internal/extension/jaegerstorage/config_test.go +++ b/cmd/jaeger/internal/extension/jaegerstorage/config_test.go @@ -83,7 +83,7 @@ backends: `) cfg := createDefaultConfig().(*Config) require.NoError(t, conf.Unmarshal(cfg)) - assert.NotEmpty(t, cfg.TraceBackends["some_storage"].Cassandra.Primary.Connection.Servers) + assert.NotEmpty(t, cfg.TraceBackends["some_storage"].Cassandra.Configuration.Connection.Servers) } func TestConfigDefaultElasticsearch(t *testing.T) { diff --git a/cmd/jaeger/internal/integration/grpc_test.go b/cmd/jaeger/internal/integration/grpc_test.go index cd12984bc62..a3579f350e6 100644 --- a/cmd/jaeger/internal/integration/grpc_test.go +++ b/cmd/jaeger/internal/integration/grpc_test.go @@ -7,20 +7,24 @@ import ( "testing" "github.com/jaegertracing/jaeger/plugin/storage/integration" + "github.com/jaegertracing/jaeger/ports" ) type GRPCStorageIntegration struct { E2EStorageIntegration - remoteStorage *integration.RemoteMemoryStorage + remoteStorage *integration.RemoteMemoryStorage + archiveRemoteStorage *integration.RemoteMemoryStorage } func (s *GRPCStorageIntegration) initialize(t *testing.T) { - s.remoteStorage = integration.StartNewRemoteMemoryStorage(t) + s.remoteStorage = integration.StartNewRemoteMemoryStorage(t, ports.RemoteStorageGRPC) + s.archiveRemoteStorage = integration.StartNewRemoteMemoryStorage(t, ports.RemoteStorageGRPC+1) } func (s *GRPCStorageIntegration) cleanUp(t *testing.T) { s.remoteStorage.Close(t) + s.archiveRemoteStorage.Close(t) s.initialize(t) } @@ -37,6 +41,7 @@ func TestGRPCStorage(t *testing.T) { s.e2eInitialize(t, "grpc") t.Cleanup(func() { s.remoteStorage.Close(t) + s.archiveRemoteStorage.Close(t) }) s.RunSpanStoreTests(t) } diff --git a/cmd/query/app/flags.go b/cmd/query/app/flags.go index 23072b16d90..2110c9f2a70 100644 --- a/cmd/query/app/flags.go +++ b/cmd/query/app/flags.go @@ -30,7 +30,7 @@ import ( "github.com/jaegertracing/jaeger/pkg/config/tlscfg" "github.com/jaegertracing/jaeger/pkg/tenancy" "github.com/jaegertracing/jaeger/ports" - "github.com/jaegertracing/jaeger/storage" + "github.com/jaegertracing/jaeger/storage/spanstore" "github.com/jaegertracing/jaeger/storage_v2/v1adapter" ) @@ -138,10 +138,19 @@ func (qOpts *QueryOptions) InitFromViper(v *viper.Viper, logger *zap.Logger) (*Q return qOpts, nil } +type InitArchiveStorageFn func(logger *zap.Logger) (spanstore.Reader, spanstore.Writer) + // BuildQueryServiceOptions creates a QueryServiceOptions struct with appropriate adjusters and archive config -func (qOpts *QueryOptions) BuildQueryServiceOptions(storageFactory storage.BaseFactory, logger *zap.Logger) *querysvc.QueryServiceOptions { +func (qOpts *QueryOptions) BuildQueryServiceOptions( + initArchiveStorageFn InitArchiveStorageFn, + logger *zap.Logger, +) *querysvc.QueryServiceOptions { opts := &querysvc.QueryServiceOptions{} - if !opts.InitArchiveStorage(storageFactory, logger) { + ar, aw := initArchiveStorageFn(logger) + if ar != nil && aw != nil { + opts.ArchiveSpanReader = ar + opts.ArchiveSpanWriter = aw + } else { logger.Info("Archive storage not initialized") } @@ -150,13 +159,13 @@ func (qOpts *QueryOptions) BuildQueryServiceOptions(storageFactory storage.BaseF return opts } -func (qOpts *QueryOptions) BuildQueryServiceOptionsV2(storageFactory storage.BaseFactory, logger *zap.Logger) *v2querysvc.QueryServiceOptions { +func (qOpts *QueryOptions) BuildQueryServiceOptionsV2(initArchiveStorageFn InitArchiveStorageFn, logger *zap.Logger) *v2querysvc.QueryServiceOptions { opts := &v2querysvc.QueryServiceOptions{} - ar, aw := v1adapter.InitializeArchiveStorage(storageFactory, logger) + ar, aw := initArchiveStorageFn(logger) if ar != nil && aw != nil { - opts.ArchiveTraceReader = ar - opts.ArchiveTraceWriter = aw + opts.ArchiveTraceReader = v1adapter.NewTraceReader(ar) + opts.ArchiveTraceWriter = v1adapter.NewTraceWriter(aw) } else { logger.Info("Archive storage not initialized") } diff --git a/cmd/query/app/flags_test.go b/cmd/query/app/flags_test.go index b56a17fc97a..86f07f367fd 100644 --- a/cmd/query/app/flags_test.go +++ b/cmd/query/app/flags_test.go @@ -17,8 +17,8 @@ import ( "github.com/jaegertracing/jaeger/pkg/config" "github.com/jaegertracing/jaeger/pkg/testutils" "github.com/jaegertracing/jaeger/ports" - "github.com/jaegertracing/jaeger/storage/mocks" - spanstore_mocks "github.com/jaegertracing/jaeger/storage/spanstore/mocks" + "github.com/jaegertracing/jaeger/storage/spanstore" + spanstoremocks "github.com/jaegertracing/jaeger/storage/spanstore/mocks" ) func TestQueryBuilderFlags(t *testing.T) { @@ -86,60 +86,51 @@ func TestStringSliceAsHeader(t *testing.T) { require.NoError(t, err) } +func initializedFn(*zap.Logger) (spanstore.Reader, spanstore.Writer) { + return &spanstoremocks.Reader{}, &spanstoremocks.Writer{} +} + +func uninitializedFn(*zap.Logger) (spanstore.Reader, spanstore.Writer) { + return nil, nil +} + func TestBuildQueryServiceOptions(t *testing.T) { v, _ := config.Viperize(AddFlags) qOpts, err := new(QueryOptions).InitFromViper(v, zap.NewNop()) require.NoError(t, err) assert.NotNil(t, qOpts) - qSvcOpts := qOpts.BuildQueryServiceOptions(&mocks.Factory{}, zap.NewNop()) - assert.NotNil(t, qSvcOpts) - assert.NotNil(t, qSvcOpts.Adjuster) - assert.Nil(t, qSvcOpts.ArchiveSpanReader) - assert.Nil(t, qSvcOpts.ArchiveSpanWriter) - - comboFactory := struct { - *mocks.Factory - *mocks.ArchiveFactory - }{ - &mocks.Factory{}, - &mocks.ArchiveFactory{}, - } - - comboFactory.ArchiveFactory.On("CreateArchiveSpanReader").Return(&spanstore_mocks.Reader{}, nil) - comboFactory.ArchiveFactory.On("CreateArchiveSpanWriter").Return(&spanstore_mocks.Writer{}, nil) - - qSvcOpts = qOpts.BuildQueryServiceOptions(comboFactory, zap.NewNop()) + qSvcOpts := qOpts.BuildQueryServiceOptions(initializedFn, zap.NewNop()) assert.NotNil(t, qSvcOpts) assert.NotNil(t, qSvcOpts.Adjuster) assert.NotNil(t, qSvcOpts.ArchiveSpanReader) assert.NotNil(t, qSvcOpts.ArchiveSpanWriter) } -func TestBuildQueryServiceOptionsV2(t *testing.T) { +func TestBuildQueryServiceOptions_NoArchiveStorage(t *testing.T) { v, _ := config.Viperize(AddFlags) qOpts, err := new(QueryOptions).InitFromViper(v, zap.NewNop()) require.NoError(t, err) assert.NotNil(t, qOpts) - qSvcOpts := qOpts.BuildQueryServiceOptionsV2(&mocks.Factory{}, zap.NewNop()) + logger, logBuf := testutils.NewLogger() + qSvcOpts := qOpts.BuildQueryServiceOptions(uninitializedFn, logger) assert.NotNil(t, qSvcOpts) assert.NotNil(t, qSvcOpts.Adjuster) - assert.Nil(t, qSvcOpts.ArchiveTraceReader) - assert.Nil(t, qSvcOpts.ArchiveTraceWriter) + assert.Nil(t, qSvcOpts.ArchiveSpanReader) + assert.Nil(t, qSvcOpts.ArchiveSpanWriter) - comboFactory := struct { - *mocks.Factory - *mocks.ArchiveFactory - }{ - &mocks.Factory{}, - &mocks.ArchiveFactory{}, - } + require.Contains(t, logBuf.String(), "Archive storage not initialized") +} + +func TestBuildQueryServiceOptionsV2(t *testing.T) { + v, _ := config.Viperize(AddFlags) + qOpts, err := new(QueryOptions).InitFromViper(v, zap.NewNop()) + require.NoError(t, err) + assert.NotNil(t, qOpts) - comboFactory.ArchiveFactory.On("CreateArchiveSpanReader").Return(&spanstore_mocks.Reader{}, nil) - comboFactory.ArchiveFactory.On("CreateArchiveSpanWriter").Return(&spanstore_mocks.Writer{}, nil) + qSvcOpts := qOpts.BuildQueryServiceOptionsV2(initializedFn, zap.NewNop()) - qSvcOpts = qOpts.BuildQueryServiceOptionsV2(comboFactory, zap.NewNop()) assert.NotNil(t, qSvcOpts) assert.NotNil(t, qSvcOpts.Adjuster) assert.NotNil(t, qSvcOpts.ArchiveTraceReader) @@ -153,7 +144,7 @@ func TestBuildQueryServiceOptionsV2_NoArchiveStorage(t *testing.T) { assert.NotNil(t, qOpts) logger, logBuf := testutils.NewLogger() - qSvcOpts := qOpts.BuildQueryServiceOptionsV2(&mocks.Factory{}, logger) + qSvcOpts := qOpts.BuildQueryServiceOptionsV2(uninitializedFn, logger) assert.Nil(t, qSvcOpts.ArchiveTraceReader) assert.Nil(t, qSvcOpts.ArchiveTraceWriter) diff --git a/cmd/query/app/querysvc/query_service.go b/cmd/query/app/querysvc/query_service.go index 9492ab13f61..914a302e30b 100644 --- a/cmd/query/app/querysvc/query_service.go +++ b/cmd/query/app/querysvc/query_service.go @@ -8,11 +8,8 @@ import ( "errors" "time" - "go.uber.org/zap" - "github.com/jaegertracing/jaeger-idl/model/v1" "github.com/jaegertracing/jaeger/model/adjuster" - "github.com/jaegertracing/jaeger/storage" "github.com/jaegertracing/jaeger/storage/spanstore" "github.com/jaegertracing/jaeger/storage_v2/depstore" "github.com/jaegertracing/jaeger/storage_v2/tracestore" @@ -164,36 +161,6 @@ func (qs QueryService) GetCapabilities() StorageCapabilities { } } -// InitArchiveStorage tries to initialize archive storage reader/writer if storage factory supports them. -func (opts *QueryServiceOptions) InitArchiveStorage(storageFactory storage.BaseFactory, logger *zap.Logger) bool { - archiveFactory, ok := storageFactory.(storage.ArchiveFactory) - if !ok { - logger.Info("Archive storage not supported by the factory") - return false - } - reader, err := archiveFactory.CreateArchiveSpanReader() - if errors.Is(err, storage.ErrArchiveStorageNotConfigured) || errors.Is(err, storage.ErrArchiveStorageNotSupported) { - logger.Info("Archive storage not created", zap.String("reason", err.Error())) - return false - } - if err != nil { - logger.Error("Cannot init archive storage reader", zap.Error(err)) - return false - } - writer, err := archiveFactory.CreateArchiveSpanWriter() - if errors.Is(err, storage.ErrArchiveStorageNotConfigured) || errors.Is(err, storage.ErrArchiveStorageNotSupported) { - logger.Info("Archive storage not created", zap.String("reason", err.Error())) - return false - } - if err != nil { - logger.Error("Cannot init archive storage writer", zap.Error(err)) - return false - } - opts.ArchiveSpanReader = reader - opts.ArchiveSpanWriter = writer - return true -} - // hasArchiveStorage returns true if archive storage reader/writer are initialized. func (opts *QueryServiceOptions) hasArchiveStorage() bool { return opts.ArchiveSpanReader != nil && opts.ArchiveSpanWriter != nil diff --git a/cmd/query/app/querysvc/query_service_test.go b/cmd/query/app/querysvc/query_service_test.go index f92c21e1b8c..f2706356a3f 100644 --- a/cmd/query/app/querysvc/query_service_test.go +++ b/cmd/query/app/querysvc/query_service_test.go @@ -451,14 +451,6 @@ func TestGetCapabilitiesWithSupportsArchive(t *testing.T) { type fakeStorageFactory1 struct{} -type fakeStorageFactory2 struct { - fakeStorageFactory1 - r spanstore.Reader - w spanstore.Writer - rErr error - wErr error -} - func (*fakeStorageFactory1) Initialize(metrics.Factory, *zap.Logger) error { return nil } @@ -466,49 +458,7 @@ func (*fakeStorageFactory1) CreateSpanReader() (spanstore.Reader, error) func (*fakeStorageFactory1) CreateSpanWriter() (spanstore.Writer, error) { return nil, nil } func (*fakeStorageFactory1) CreateDependencyReader() (dependencystore.Reader, error) { return nil, nil } -func (f *fakeStorageFactory2) CreateArchiveSpanReader() (spanstore.Reader, error) { return f.r, f.rErr } -func (f *fakeStorageFactory2) CreateArchiveSpanWriter() (spanstore.Writer, error) { return f.w, f.wErr } - -var ( - _ storage.Factory = new(fakeStorageFactory1) - _ storage.ArchiveFactory = new(fakeStorageFactory2) -) - -func TestInitArchiveStorageErrors(t *testing.T) { - opts := &QueryServiceOptions{} - logger := zap.NewNop() - - assert.False(t, opts.InitArchiveStorage(new(fakeStorageFactory1), logger)) - assert.False(t, opts.InitArchiveStorage( - &fakeStorageFactory2{rErr: storage.ErrArchiveStorageNotConfigured}, - logger, - )) - assert.False(t, opts.InitArchiveStorage( - &fakeStorageFactory2{rErr: errors.New("error")}, - logger, - )) - assert.False(t, opts.InitArchiveStorage( - &fakeStorageFactory2{wErr: storage.ErrArchiveStorageNotConfigured}, - logger, - )) - assert.False(t, opts.InitArchiveStorage( - &fakeStorageFactory2{wErr: errors.New("error")}, - logger, - )) -} - -func TestInitArchiveStorage(t *testing.T) { - opts := &QueryServiceOptions{} - logger := zap.NewNop() - reader := &spanstoremocks.Reader{} - writer := &spanstoremocks.Writer{} - assert.True(t, opts.InitArchiveStorage( - &fakeStorageFactory2{r: reader, w: writer}, - logger, - )) - assert.Equal(t, reader, opts.ArchiveSpanReader) - assert.Equal(t, writer, opts.ArchiveSpanWriter) -} +var _ storage.Factory = new(fakeStorageFactory1) func TestMain(m *testing.M) { testutils.VerifyGoLeaks(m) diff --git a/cmd/query/app/token_propagation_test.go b/cmd/query/app/token_propagation_test.go index 6c7177cb535..2af20349b2c 100644 --- a/cmd/query/app/token_propagation_test.go +++ b/cmd/query/app/token_propagation_test.go @@ -81,7 +81,7 @@ func runQueryService(t *testing.T, esURL string) *Server { })) f.InitFromViper(v, flagsSvc.Logger) // set AllowTokenFromContext manually because we don't register the respective CLI flag from query svc - f.Options.Primary.Authentication.BearerTokenAuthentication.AllowFromContext = true + f.Options.Config.Authentication.BearerTokenAuthentication.AllowFromContext = true require.NoError(t, f.Initialize(telset.Metrics, telset.Logger)) defer f.Close() diff --git a/cmd/query/main.go b/cmd/query/main.go index 2cced74fb30..8ee34ae6188 100644 --- a/cmd/query/main.go +++ b/cmd/query/main.go @@ -107,13 +107,19 @@ func main() { if err != nil { logger.Fatal("Failed to create metrics query service", zap.Error(err)) } - queryServiceOptions := queryOpts.BuildQueryServiceOptions(storageFactory, logger) + queryServiceOptions := queryOpts.BuildQueryServiceOptions( + storageFactory.InitArchiveStorage, + logger, + ) queryService := querysvc.NewQueryService( traceReader, dependencyReader, *queryServiceOptions) - queryServiceOptionsV2 := queryOpts.BuildQueryServiceOptionsV2(storageFactory, logger) + queryServiceOptionsV2 := queryOpts.BuildQueryServiceOptionsV2( + storageFactory.InitArchiveStorage, + logger, + ) queryServiceV2 := querysvcv2.NewQueryService( traceReader, dependencyReader, diff --git a/cmd/remote-storage/app/server.go b/cmd/remote-storage/app/server.go index 66529595a88..577df153f20 100644 --- a/cmd/remote-storage/app/server.go +++ b/cmd/remote-storage/app/server.go @@ -17,12 +17,10 @@ import ( "google.golang.org/grpc/health" "google.golang.org/grpc/reflection" - "github.com/jaegertracing/jaeger/cmd/query/app/querysvc" "github.com/jaegertracing/jaeger/pkg/bearertoken" "github.com/jaegertracing/jaeger/pkg/telemetry" "github.com/jaegertracing/jaeger/pkg/tenancy" "github.com/jaegertracing/jaeger/plugin/storage/grpc/shared" - "github.com/jaegertracing/jaeger/storage" "github.com/jaegertracing/jaeger/storage/dependencystore" "github.com/jaegertracing/jaeger/storage/spanstore" ) @@ -36,8 +34,15 @@ type Server struct { telset telemetry.Settings } +type storageFactory interface { + CreateSpanReader() (spanstore.Reader, error) + CreateSpanWriter() (spanstore.Writer, error) + CreateDependencyReader() (dependencystore.Reader, error) + InitArchiveStorage(logger *zap.Logger) (spanstore.Reader, spanstore.Writer) +} + // NewServer creates and initializes Server. -func NewServer(options *Options, storageFactory storage.BaseFactory, tm *tenancy.Manager, telset telemetry.Settings) (*Server, error) { +func NewServer(options *Options, storageFactory storageFactory, tm *tenancy.Manager, telset telemetry.Settings) (*Server, error) { handler, err := createGRPCHandler(storageFactory, telset.Logger) if err != nil { return nil, err @@ -55,7 +60,7 @@ func NewServer(options *Options, storageFactory storage.BaseFactory, tm *tenancy }, nil } -func createGRPCHandler(f storage.BaseFactory, logger *zap.Logger) (*shared.GRPCHandler, error) { +func createGRPCHandler(f storageFactory, logger *zap.Logger) (*shared.GRPCHandler, error) { reader, err := f.CreateSpanReader() if err != nil { return nil, err @@ -76,12 +81,9 @@ func createGRPCHandler(f storage.BaseFactory, logger *zap.Logger) (*shared.GRPCH StreamingSpanWriter: func() spanstore.Writer { return nil }, } - // borrow code from Query service for archive storage - qOpts := &querysvc.QueryServiceOptions{} - // when archive storage not initialized (returns false), the reader/writer will be nil - _ = qOpts.InitArchiveStorage(f, logger) - impl.ArchiveSpanReader = func() spanstore.Reader { return qOpts.ArchiveSpanReader } - impl.ArchiveSpanWriter = func() spanstore.Writer { return qOpts.ArchiveSpanWriter } + ar, aw := f.InitArchiveStorage(logger) + impl.ArchiveSpanReader = func() spanstore.Reader { return ar } + impl.ArchiveSpanWriter = func() spanstore.Writer { return aw } handler := shared.NewGRPCHandler(impl) return handler, nil diff --git a/cmd/remote-storage/app/server_test.go b/cmd/remote-storage/app/server_test.go index e9c245c4cc4..25603dc701d 100644 --- a/cmd/remote-storage/app/server_test.go +++ b/cmd/remote-storage/app/server_test.go @@ -28,22 +28,16 @@ import ( "github.com/jaegertracing/jaeger/pkg/tenancy" "github.com/jaegertracing/jaeger/ports" "github.com/jaegertracing/jaeger/proto-gen/storage_v1" + "github.com/jaegertracing/jaeger/storage/dependencystore" depStoreMocks "github.com/jaegertracing/jaeger/storage/dependencystore/mocks" - factoryMocks "github.com/jaegertracing/jaeger/storage/mocks" + "github.com/jaegertracing/jaeger/storage/spanstore" spanStoreMocks "github.com/jaegertracing/jaeger/storage/spanstore/mocks" ) var testCertKeyLocation = "../../../pkg/config/tlscfg/testdata" func TestNewServer_CreateStorageErrors(t *testing.T) { - factory := new(factoryMocks.Factory) - factory.On("CreateSpanReader").Return(nil, errors.New("no reader")).Once() - factory.On("CreateSpanReader").Return(nil, nil) - factory.On("CreateSpanWriter").Return(nil, errors.New("no writer")).Once() - factory.On("CreateSpanWriter").Return(nil, nil) - factory.On("CreateDependencyReader").Return(nil, errors.New("no deps")).Once() - factory.On("CreateDependencyReader").Return(nil, nil) - f := func() (*Server, error) { + createServer := func(factory *fakeFactory) (*Server, error) { return NewServer( &Options{ ServerConfig: configgrpc.ServerConfig{ @@ -57,16 +51,21 @@ func TestNewServer_CreateStorageErrors(t *testing.T) { telemetry.NoopSettings(), ) } - _, err := f() + + factory := &fakeFactory{readerErr: errors.New("no reader")} + _, err := createServer(factory) require.ErrorContains(t, err, "no reader") - _, err = f() + factory = &fakeFactory{writerErr: errors.New("no writer")} + _, err = createServer(factory) require.ErrorContains(t, err, "no writer") - _, err = f() + factory = &fakeFactory{depReaderErr: errors.New("no deps")} + _, err = createServer(factory) require.ErrorContains(t, err, "no deps") - s, err := f() + factory = &fakeFactory{} + s, err := createServer(factory) require.NoError(t, err) require.NoError(t, s.Start()) validateGRPCServer(t, s.grpcConn.Addr().String()) @@ -86,29 +85,39 @@ func TestServerStart_BadPortErrors(t *testing.T) { require.Error(t, srv.Start()) } -type storageMocks struct { - factory *factoryMocks.Factory - reader *spanStoreMocks.Reader - writer *spanStoreMocks.Writer - depReader *depStoreMocks.Reader +type fakeFactory struct { + reader spanstore.Reader + writer spanstore.Writer + depReader dependencystore.Reader + + readerErr error + writerErr error + depReaderErr error } -func newStorageMocks() *storageMocks { - reader := new(spanStoreMocks.Reader) - writer := new(spanStoreMocks.Writer) - depReader := new(depStoreMocks.Reader) +func (f *fakeFactory) CreateSpanReader() (spanstore.Reader, error) { + if f.readerErr != nil { + return nil, f.readerErr + } + return f.reader, nil +} - factory := new(factoryMocks.Factory) - factory.On("CreateSpanReader").Return(reader, nil) - factory.On("CreateSpanWriter").Return(writer, nil) - factory.On("CreateDependencyReader").Return(depReader, nil) +func (f *fakeFactory) CreateSpanWriter() (spanstore.Writer, error) { + if f.writerErr != nil { + return nil, f.writerErr + } + return f.writer, nil +} - return &storageMocks{ - factory: factory, - reader: reader, - writer: writer, - depReader: depReader, +func (f *fakeFactory) CreateDependencyReader() (dependencystore.Reader, error) { + if f.depReaderErr != nil { + return nil, f.depReaderErr } + return f.depReader, nil +} + +func (*fakeFactory) InitArchiveStorage(*zap.Logger) (spanstore.Reader, spanstore.Writer) { + return nil, nil } func TestNewServer_TLSConfigError(t *testing.T) { @@ -123,7 +132,7 @@ func TestNewServer_TLSConfigError(t *testing.T) { Logger: zap.NewNop(), ReportStatus: telemetry.HCAdapter(healthcheck.New()), } - storageMocks := newStorageMocks() + _, err := NewServer( &Options{ ServerConfig: configgrpc.ServerConfig{ @@ -133,7 +142,7 @@ func TestNewServer_TLSConfigError(t *testing.T) { TLSSetting: tlsCfg, }, }, - storageMocks.factory, + &fakeFactory{}, tenancy.NewManager(&tenancy.Options{}), telset, ) @@ -141,15 +150,24 @@ func TestNewServer_TLSConfigError(t *testing.T) { } func TestCreateGRPCHandler(t *testing.T) { - storageMocks := newStorageMocks() - h, err := createGRPCHandler(storageMocks.factory, zap.NewNop()) + reader := new(spanStoreMocks.Reader) + writer := new(spanStoreMocks.Writer) + depReader := new(depStoreMocks.Reader) + + f := &fakeFactory{ + reader: reader, + writer: writer, + depReader: depReader, + } + + h, err := createGRPCHandler(f, zap.NewNop()) require.NoError(t, err) - storageMocks.writer.On("WriteSpan", mock.Anything, mock.Anything).Return(errors.New("writer error")) + writer.On("WriteSpan", mock.Anything, mock.Anything).Return(errors.New("writer error")) _, err = h.WriteSpan(context.Background(), &storage_v1.WriteSpanRequest{}) require.ErrorContains(t, err, "writer error") - storageMocks.depReader.On( + depReader.On( "GetDependencies", mock.Anything, // context mock.Anything, // time @@ -338,9 +356,12 @@ func TestServerGRPCTLS(t *testing.T) { flagsSvc := flags.NewService(ports.QueryAdminHTTP) flagsSvc.Logger = zap.NewNop() - storageMocks := newStorageMocks() + reader := new(spanStoreMocks.Reader) + f := &fakeFactory{ + reader: reader, + } expectedServices := []string{"test"} - storageMocks.reader.On("GetServices", mock.AnythingOfType("*context.valueCtx")).Return(expectedServices, nil) + reader.On("GetServices", mock.AnythingOfType("*context.valueCtx")).Return(expectedServices, nil) tm := tenancy.NewManager(&tenancy.Options{Enabled: true}) telset := telemetry.Settings{ @@ -349,7 +370,7 @@ func TestServerGRPCTLS(t *testing.T) { } server, err := NewServer( serverOptions, - storageMocks.factory, + f, tm, telset, ) @@ -391,7 +412,6 @@ func TestServerHandlesPortZero(t *testing.T) { flagsSvc := flags.NewService(ports.QueryAdminHTTP) zapCore, logs := observer.New(zap.InfoLevel) flagsSvc.Logger = zap.New(zapCore) - storageMocks := newStorageMocks() telset := telemetry.Settings{ Logger: flagsSvc.Logger, ReportStatus: telemetry.HCAdapter(flagsSvc.HC()), @@ -400,7 +420,7 @@ func TestServerHandlesPortZero(t *testing.T) { &Options{ServerConfig: configgrpc.ServerConfig{ NetAddr: confignet.AddrConfig{Endpoint: ":0"}, }}, - storageMocks.factory, + &fakeFactory{}, tenancy.NewManager(&tenancy.Options{}), telset, ) diff --git a/pkg/es/config/config.go b/pkg/es/config/config.go index d0ae9c6ad3d..a1916e95d48 100644 --- a/pkg/es/config/config.go +++ b/pkg/es/config/config.go @@ -115,6 +115,14 @@ type Configuration struct { // Use this option with Elasticsearch rollover API. It requires an external component // to create aliases before startup and then performing its management. UseReadWriteAliases bool `mapstructure:"use_aliases"` + // ReadAliasSuffix is the suffix to append to the index name used for reading. + // This configuration only exists to provide backwards compatibility for jaeger-v1 + // which is why it is not exposed as a configuration option for jaeger-v2 + ReadAliasSuffix string `mapstructure:"-"` + // WriteAliasSuffix is the suffix to append to the write index name. + // This configuration only exists to provide backwards compatibility for jaeger-v1 + // which is why it is not exposed as a configuration option for jaeger-v2 + WriteAliasSuffix string `mapstructure:"-"` // CreateIndexTemplates, if set to true, creates index templates at application startup. // This configuration should be set to false when templates are installed manually. CreateIndexTemplates bool `mapstructure:"create_mappings"` diff --git a/plugin/storage/badger/factory.go b/plugin/storage/badger/factory.go index 10de32d8e8e..b8fbe06770f 100644 --- a/plugin/storage/badger/factory.go +++ b/plugin/storage/badger/factory.go @@ -38,14 +38,10 @@ const ( ) var ( // interface comformance checks - _ storage.Factory = (*Factory)(nil) - _ io.Closer = (*Factory)(nil) - _ plugin.Configurable = (*Factory)(nil) - _ storage.Purger = (*Factory)(nil) - - // TODO badger could implement archive storage - // _ storage.ArchiveFactory = (*Factory)(nil) - + _ storage.Factory = (*Factory)(nil) + _ io.Closer = (*Factory)(nil) + _ plugin.Configurable = (*Factory)(nil) + _ storage.Purger = (*Factory)(nil) _ storage.SamplingStoreFactory = (*Factory)(nil) ) diff --git a/plugin/storage/blackhole/factory.go b/plugin/storage/blackhole/factory.go index ad149593633..7cbdd4ae2eb 100644 --- a/plugin/storage/blackhole/factory.go +++ b/plugin/storage/blackhole/factory.go @@ -13,10 +13,8 @@ import ( "github.com/jaegertracing/jaeger/storage/spanstore" ) -var ( // interface comformance checks - _ storage.Factory = (*Factory)(nil) - _ storage.ArchiveFactory = (*Factory)(nil) -) +// interface comformance checks +var _ storage.Factory = (*Factory)(nil) // Factory implements storage.Factory and creates blackhole storage components. type Factory struct { @@ -48,16 +46,6 @@ func (f *Factory) CreateSpanWriter() (spanstore.Writer, error) { return f.store, nil } -// CreateArchiveSpanReader implements storage.ArchiveFactory -func (f *Factory) CreateArchiveSpanReader() (spanstore.Reader, error) { - return f.store, nil -} - -// CreateArchiveSpanWriter implements storage.ArchiveFactory -func (f *Factory) CreateArchiveSpanWriter() (spanstore.Writer, error) { - return f.store, nil -} - // CreateDependencyReader implements storage.Factory func (f *Factory) CreateDependencyReader() (dependencystore.Reader, error) { return f.store, nil diff --git a/plugin/storage/blackhole/factory_test.go b/plugin/storage/blackhole/factory_test.go index 0ef1a783176..134e44047c1 100644 --- a/plugin/storage/blackhole/factory_test.go +++ b/plugin/storage/blackhole/factory_test.go @@ -27,12 +27,6 @@ func TestStorageFactory(t *testing.T) { writer, err := f.CreateSpanWriter() require.NoError(t, err) assert.Equal(t, f.store, writer) - reader, err = f.CreateArchiveSpanReader() - require.NoError(t, err) - assert.Equal(t, f.store, reader) - writer, err = f.CreateArchiveSpanWriter() - require.NoError(t, err) - assert.Equal(t, f.store, writer) depReader, err := f.CreateDependencyReader() require.NoError(t, err) assert.Equal(t, f.store, depReader) diff --git a/plugin/storage/cassandra/factory.go b/plugin/storage/cassandra/factory.go index 884eed5a772..5937f92d08a 100644 --- a/plugin/storage/cassandra/factory.go +++ b/plugin/storage/cassandra/factory.go @@ -36,17 +36,18 @@ import ( ) const ( - primaryStorageConfig = "cassandra" - archiveStorageConfig = "cassandra-archive" + primaryStorageNamespace = "cassandra" + archiveStorageNamespace = "cassandra-archive" ) var ( // interface comformance checks _ storage.Factory = (*Factory)(nil) _ storage.Purger = (*Factory)(nil) - _ storage.ArchiveFactory = (*Factory)(nil) _ storage.SamplingStoreFactory = (*Factory)(nil) _ io.Closer = (*Factory)(nil) _ plugin.Configurable = (*Factory)(nil) + _ storage.Inheritable = (*Factory)(nil) + _ storage.ArchiveCapable = (*Factory)(nil) ) // Factory implements storage.Factory for Cassandra backend. @@ -57,11 +58,9 @@ type Factory struct { logger *zap.Logger tracer trace.TracerProvider - primaryConfig config.Configuration - archiveConfig *config.Configuration + config config.Configuration - primarySession cassandra.Session - archiveSession cassandra.Session + session cassandra.Session // tests can override this sessionBuilderFn func(*config.Configuration) (cassandra.Session, error) @@ -71,7 +70,15 @@ type Factory struct { func NewFactory() *Factory { return &Factory{ tracer: otel.GetTracerProvider(), - Options: NewOptions(primaryStorageConfig, archiveStorageConfig), + Options: NewOptions(primaryStorageNamespace), + sessionBuilderFn: NewSession, + } +} + +func NewArchiveFactory() *Factory { + return &Factory{ + tracer: otel.GetTracerProvider(), + Options: NewOptions(archiveStorageNamespace), sessionBuilderFn: NewSession, } } @@ -104,7 +111,7 @@ type withConfigBuilder struct { func (b *withConfigBuilder) build() (*Factory, error) { b.f.configureFromOptions(b.opts) - if err := b.opts.Primary.Validate(); err != nil { + if err := b.opts.NamespaceConfig.Validate(); err != nil { return nil, err } err := b.initializer(b.metricsFactory, b.logger) @@ -128,12 +135,7 @@ func (f *Factory) InitFromViper(v *viper.Viper, _ *zap.Logger) { // InitFromOptions initializes factory from options. func (f *Factory) configureFromOptions(o *Options) { f.Options = o - // TODO this is a hack because we do not define defaults in Options - if o.others == nil { - o.others = make(map[string]*NamespaceConfig) - } - f.primaryConfig = o.GetPrimary() - f.archiveConfig = f.Options.Get(archiveStorageConfig) + f.config = o.GetConfig() } // Initialize implements storage.Factory @@ -141,21 +143,12 @@ func (f *Factory) Initialize(metricsFactory metrics.Factory, logger *zap.Logger) f.metricsFactory = metricsFactory f.logger = logger - primarySession, err := f.sessionBuilderFn(&f.primaryConfig) + session, err := f.sessionBuilderFn(&f.config) if err != nil { return err } - f.primarySession = primarySession - - if f.archiveConfig != nil { - archiveSession, err := f.sessionBuilderFn(f.archiveConfig) - if err != nil { - return err - } - f.archiveSession = archiveSession - } else { - logger.Info("Cassandra archive storage configuration is empty, skipping") - } + f.session = session + return nil } @@ -203,7 +196,7 @@ func NewSession(c *config.Configuration) (cassandra.Session, error) { // CreateSpanReader implements storage.Factory func (f *Factory) CreateSpanReader() (spanstore.Reader, error) { - sr, err := cSpanStore.NewSpanReader(f.primarySession, f.metricsFactory, f.logger, f.tracer.Tracer("cSpanStore.SpanReader")) + sr, err := cSpanStore.NewSpanReader(f.session, f.metricsFactory, f.logger, f.tracer.Tracer("cSpanStore.SpanReader")) if err != nil { return nil, err } @@ -216,51 +209,13 @@ func (f *Factory) CreateSpanWriter() (spanstore.Writer, error) { if err != nil { return nil, err } - return cSpanStore.NewSpanWriter(f.primarySession, f.Options.SpanStoreWriteCacheTTL, f.metricsFactory, f.logger, options...) + return cSpanStore.NewSpanWriter(f.session, f.Options.SpanStoreWriteCacheTTL, f.metricsFactory, f.logger, options...) } // CreateDependencyReader implements storage.Factory func (f *Factory) CreateDependencyReader() (dependencystore.Reader, error) { - version := cDepStore.GetDependencyVersion(f.primarySession) - return cDepStore.NewDependencyStore(f.primarySession, f.metricsFactory, f.logger, version) -} - -// CreateArchiveSpanReader implements storage.ArchiveFactory -func (f *Factory) CreateArchiveSpanReader() (spanstore.Reader, error) { - if f.archiveSession == nil { - return nil, storage.ErrArchiveStorageNotConfigured - } - archiveMetricsFactory := f.metricsFactory.Namespace( - metrics.NSOptions{ - Tags: map[string]string{ - "role": "archive", - }, - }, - ) - sr, err := cSpanStore.NewSpanReader(f.archiveSession, archiveMetricsFactory, f.logger, f.tracer.Tracer("cSpanStore.SpanReader")) - if err != nil { - return nil, err - } - return spanstoremetrics.NewReaderDecorator(sr, archiveMetricsFactory), nil -} - -// CreateArchiveSpanWriter implements storage.ArchiveFactory -func (f *Factory) CreateArchiveSpanWriter() (spanstore.Writer, error) { - if f.archiveSession == nil { - return nil, storage.ErrArchiveStorageNotConfigured - } - options, err := writerOptions(f.Options) - if err != nil { - return nil, err - } - archiveMetricsFactory := f.metricsFactory.Namespace( - metrics.NSOptions{ - Tags: map[string]string{ - "role": "archive", - }, - }, - ) - return cSpanStore.NewSpanWriter(f.archiveSession, f.Options.SpanStoreWriteCacheTTL, archiveMetricsFactory, f.logger, options...) + version := cDepStore.GetDependencyVersion(f.session) + return cDepStore.NewDependencyStore(f.session, f.metricsFactory, f.logger, version) } // CreateLock implements storage.SamplingStoreFactory @@ -271,7 +226,7 @@ func (f *Factory) CreateLock() (distributedlock.Lock, error) { } f.logger.Info("Using unique participantName in the distributed lock", zap.String("participantName", hostId)) - return cLock.NewLock(f.primarySession, hostId), nil + return cLock.NewLock(f.session, hostId), nil } // CreateSamplingStore implements storage.SamplingStoreFactory @@ -283,7 +238,7 @@ func (f *Factory) CreateSamplingStore(int /* maxBuckets */) (samplingstore.Store }, }, ) - return cSamplingStore.New(f.primarySession, samplingMetricsFactory, f.logger), nil + return cSamplingStore.New(f.session, samplingMetricsFactory, f.logger), nil } func writerOptions(opts *Options) ([]cSpanStore.Option, error) { @@ -319,16 +274,24 @@ var _ io.Closer = (*Factory)(nil) // Close closes the resources held by the factory func (f *Factory) Close() error { - if f.primarySession != nil { - f.primarySession.Close() - } - if f.archiveSession != nil { - f.archiveSession.Close() + if f.session != nil { + f.session.Close() } return nil } func (f *Factory) Purge(_ context.Context) error { - return f.primarySession.Query("TRUNCATE traces").Exec() + return f.session.Query("TRUNCATE traces").Exec() +} + +func (f *Factory) InheritSettingsFrom(other storage.Factory) { + if otherFactory, ok := other.(*Factory); ok { + f.config.ApplyDefaults(&otherFactory.config) + } +} + +func (f *Factory) IsArchiveCapable() bool { + return f.Options.NamespaceConfig.namespace == archiveStorageNamespace && + f.Options.NamespaceConfig.Enabled } diff --git a/plugin/storage/cassandra/factory_test.go b/plugin/storage/cassandra/factory_test.go index 20f7ca54d5f..bc0af147e1c 100644 --- a/plugin/storage/cassandra/factory_test.go +++ b/plugin/storage/cassandra/factory_test.go @@ -46,64 +46,68 @@ func (m *mockSessionBuilder) build(*config.Configuration) (cassandra.Session, er } func TestCassandraFactory(t *testing.T) { - logger, logBuf := testutils.NewLogger() - f := NewFactory() - v, command := viperize.Viperize(f.AddFlags) - command.ParseFlags([]string{"--cassandra-archive.enabled=true"}) - f.InitFromViper(v, zap.NewNop()) - - f.sessionBuilderFn = new(mockSessionBuilder).add(nil, errors.New("made-up primary error")).build - require.EqualError(t, f.Initialize(metrics.NullFactory, zap.NewNop()), "made-up primary error") + logger, _ := testutils.NewLogger() + + tests := []struct { + name string + factoryFn func() *Factory + namespace string + }{ + { + name: "CassandraFactory", + factoryFn: NewFactory, + namespace: primaryStorageNamespace, + }, + { + name: "CassandraArchiveFactory", + factoryFn: NewArchiveFactory, + namespace: archiveStorageNamespace, + }, + } - var ( - session = &mocks.Session{} - query = &mocks.Query{} - ) - session.On("Query", mock.AnythingOfType("string"), mock.Anything).Return(query) - session.On("Close").Return() - query.On("Exec").Return(nil) - f.sessionBuilderFn = new(mockSessionBuilder). - add(session, nil). - add(nil, errors.New("made-up archive error")).build - require.EqualError(t, f.Initialize(metrics.NullFactory, zap.NewNop()), "made-up archive error") + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + f := test.factoryFn() + require.Equal(t, test.namespace, f.Options.namespace) + v, command := viperize.Viperize(f.AddFlags) + command.ParseFlags([]string{}) + f.InitFromViper(v, zap.NewNop()) - f.archiveConfig = nil - f.sessionBuilderFn = new(mockSessionBuilder).add(session, nil).build - require.NoError(t, f.Initialize(metrics.NullFactory, logger)) - assert.Contains(t, logBuf.String(), "Cassandra archive storage configuration is empty, skipping") + f.sessionBuilderFn = new(mockSessionBuilder).add(nil, errors.New("made-up primary error")).build + require.EqualError(t, f.Initialize(metrics.NullFactory, zap.NewNop()), "made-up primary error") - _, err := f.CreateSpanReader() - require.NoError(t, err) + var ( + session = &mocks.Session{} + query = &mocks.Query{} + ) + session.On("Query", mock.AnythingOfType("string"), mock.Anything).Return(query) + session.On("Close").Return() + query.On("Exec").Return(nil) - _, err = f.CreateSpanWriter() - require.NoError(t, err) + f.sessionBuilderFn = new(mockSessionBuilder).add(session, nil).build + require.NoError(t, f.Initialize(metrics.NullFactory, logger)) - _, err = f.CreateDependencyReader() - require.NoError(t, err) + _, err := f.CreateSpanReader() + require.NoError(t, err) - _, err = f.CreateArchiveSpanReader() - require.EqualError(t, err, "archive storage not configured") + _, err = f.CreateSpanWriter() + require.NoError(t, err) - _, err = f.CreateArchiveSpanWriter() - require.EqualError(t, err, "archive storage not configured") + _, err = f.CreateDependencyReader() + require.NoError(t, err) - f.archiveConfig = &config.Configuration{} - f.sessionBuilderFn = new(mockSessionBuilder).add(session, nil).add(session, nil).build - require.NoError(t, f.Initialize(metrics.NullFactory, zap.NewNop())) + f.sessionBuilderFn = new(mockSessionBuilder).add(session, nil).add(session, nil).build + require.NoError(t, f.Initialize(metrics.NullFactory, zap.NewNop())) - _, err = f.CreateArchiveSpanReader() - require.NoError(t, err) + _, err = f.CreateLock() + require.NoError(t, err) - _, err = f.CreateArchiveSpanWriter() - require.NoError(t, err) + _, err = f.CreateSamplingStore(0) + require.NoError(t, err) - _, err = f.CreateLock() - require.NoError(t, err) - - _, err = f.CreateSamplingStore(0) - require.NoError(t, err) - - require.NoError(t, f.Close()) + require.NoError(t, f.Close()) + }) + } } func TestCreateSpanReaderError(t *testing.T) { @@ -117,22 +121,17 @@ func TestCreateSpanReaderError(t *testing.T) { mock.Anything).Return(query) query.On("Exec").Return(errors.New("table does not exist")) f := NewFactory() - f.archiveConfig = &config.Configuration{} f.sessionBuilderFn = new(mockSessionBuilder).add(session, nil).add(session, nil).build require.NoError(t, f.Initialize(metrics.NullFactory, zap.NewNop())) r, err := f.CreateSpanReader() require.Error(t, err) require.Nil(t, r) - ar, err := f.CreateArchiveSpanReader() - require.Error(t, err) - require.Nil(t, ar) } func TestExclusiveWhitelistBlacklist(t *testing.T) { f := NewFactory() v, command := viperize.Viperize(f.AddFlags) command.ParseFlags([]string{ - "--cassandra-archive.enabled=true", "--cassandra.index.tag-whitelist=a,b,c", "--cassandra.index.tag-blacklist=a,b,c", }) @@ -149,12 +148,8 @@ func TestExclusiveWhitelistBlacklist(t *testing.T) { _, err := f.CreateSpanWriter() require.EqualError(t, err, "only one of TagIndexBlacklist and TagIndexWhitelist can be specified") - f.archiveConfig = &config.Configuration{} f.sessionBuilderFn = new(mockSessionBuilder).add(session, nil).add(session, nil).build require.NoError(t, f.Initialize(metrics.NullFactory, zap.NewNop())) - - _, err = f.CreateArchiveSpanWriter() - require.EqualError(t, err, "only one of TagIndexBlacklist and TagIndexWhitelist can be specified") } func TestWriterOptions(t *testing.T) { @@ -201,18 +196,16 @@ func TestWriterOptions(t *testing.T) { func TestConfigureFromOptions(t *testing.T) { f := NewFactory() - o := NewOptions("foo", archiveStorageConfig) - o.others[archiveStorageConfig].Enabled = true + o := NewOptions("foo") f.configureFromOptions(o) assert.Equal(t, o, f.Options) - assert.Equal(t, o.GetPrimary(), f.primaryConfig) - assert.Equal(t, o.Get(archiveStorageConfig), f.archiveConfig) + assert.Equal(t, o.GetConfig(), f.config) } func TestNewFactoryWithConfig(t *testing.T) { t.Run("valid configuration", func(t *testing.T) { opts := &Options{ - Primary: NamespaceConfig{ + NamespaceConfig: NamespaceConfig{ Configuration: config.DefaultConfiguration(), }, } @@ -230,7 +223,7 @@ func TestNewFactoryWithConfig(t *testing.T) { t.Run("connection error", func(t *testing.T) { expErr := errors.New("made-up error") opts := &Options{ - Primary: NamespaceConfig{ + NamespaceConfig: NamespaceConfig{ Configuration: config.DefaultConfiguration(), }, } @@ -260,7 +253,7 @@ func TestFactory_Purge(t *testing.T) { ) session.On("Query", mock.AnythingOfType("string"), mock.Anything).Return(query) query.On("Exec").Return(nil) - f.primarySession = session + f.session = session err := f.Purge(context.Background()) require.NoError(t, err) @@ -298,3 +291,60 @@ func TestNewSessionErrors(t *testing.T) { require.ErrorContains(t, err, "no hosts provided") }) } + +func TestInheritSettingsFrom(t *testing.T) { + primaryFactory := NewFactory() + primaryFactory.config.Schema.Keyspace = "foo" + primaryFactory.config.Query.MaxRetryAttempts = 99 + + archiveFactory := NewArchiveFactory() + archiveFactory.config.Schema.Keyspace = "bar" + + archiveFactory.InheritSettingsFrom(primaryFactory) + + require.Equal(t, "bar", archiveFactory.config.Schema.Keyspace) + require.Equal(t, 99, archiveFactory.config.Query.MaxRetryAttempts) +} + +func TestIsArchiveCapable(t *testing.T) { + tests := []struct { + name string + namespace string + enabled bool + expected bool + }{ + { + name: "archive capable", + namespace: "cassandra-archive", + enabled: true, + expected: true, + }, + { + name: "not capable", + namespace: "cassandra-archive", + enabled: false, + expected: false, + }, + { + name: "capable + wrong namespace", + namespace: "cassandra", + enabled: true, + expected: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + factory := &Factory{ + Options: &Options{ + NamespaceConfig: NamespaceConfig{ + namespace: test.namespace, + Enabled: test.enabled, + }, + }, + } + result := factory.IsArchiveCapable() + require.Equal(t, test.expected, result) + }) + } +} diff --git a/plugin/storage/cassandra/options.go b/plugin/storage/cassandra/options.go index ae2ba2d1d05..8745c4c8fbb 100644 --- a/plugin/storage/cassandra/options.go +++ b/plugin/storage/cassandra/options.go @@ -48,8 +48,7 @@ const ( // to bind them to command line flag and apply overlays, so that some configurations // (e.g. archive) may be underspecified and infer the rest of its parameters from primary. type Options struct { - Primary NamespaceConfig `mapstructure:",squash"` - others map[string]*NamespaceConfig + NamespaceConfig `mapstructure:",squash"` SpanStoreWriteCacheTTL time.Duration `mapstructure:"span_store_write_cache_ttl"` Index IndexConfig `mapstructure:"index"` } @@ -74,52 +73,44 @@ type NamespaceConfig struct { } // NewOptions creates a new Options struct. -func NewOptions(primaryNamespace string, otherNamespaces ...string) *Options { +func NewOptions(namespace string) *Options { // TODO all default values should be defined via cobra flags options := &Options{ - Primary: NamespaceConfig{ + NamespaceConfig: NamespaceConfig{ Configuration: config.DefaultConfiguration(), - namespace: primaryNamespace, + namespace: namespace, Enabled: true, }, - others: make(map[string]*NamespaceConfig, len(otherNamespaces)), SpanStoreWriteCacheTTL: time.Hour * 12, } - for _, namespace := range otherNamespaces { - options.others[namespace] = &NamespaceConfig{namespace: namespace} - } - return options } // AddFlags adds flags for Options func (opt *Options) AddFlags(flagSet *flag.FlagSet) { - addFlags(flagSet, opt.Primary) - for _, cfg := range opt.others { - addFlags(flagSet, *cfg) - } - flagSet.Duration(opt.Primary.namespace+suffixSpanStoreWriteCacheTTL, + addFlags(flagSet, opt.NamespaceConfig) + flagSet.Duration(opt.namespace+suffixSpanStoreWriteCacheTTL, opt.SpanStoreWriteCacheTTL, "The duration to wait before rewriting an existing service or operation name") flagSet.String( - opt.Primary.namespace+suffixIndexTagsBlacklist, + opt.namespace+suffixIndexTagsBlacklist, opt.Index.TagBlackList, "The comma-separated list of span tags to blacklist from being indexed. All other tags will be indexed. Mutually exclusive with the whitelist option.") flagSet.String( - opt.Primary.namespace+suffixIndexTagsWhitelist, + opt.namespace+suffixIndexTagsWhitelist, opt.Index.TagWhiteList, "The comma-separated list of span tags to whitelist for being indexed. All other tags will not be indexed. Mutually exclusive with the blacklist option.") flagSet.Bool( - opt.Primary.namespace+suffixIndexLogs, + opt.namespace+suffixIndexLogs, !opt.Index.Logs, "Controls log field indexing. Set to false to disable.") flagSet.Bool( - opt.Primary.namespace+suffixIndexTags, + opt.namespace+suffixIndexTags, !opt.Index.Tags, "Controls tag indexing. Set to false to disable.") flagSet.Bool( - opt.Primary.namespace+suffixIndexProcessTags, + opt.namespace+suffixIndexProcessTags, !opt.Index.ProcessTags, "Controls process tag indexing. Set to false to disable.") } @@ -128,7 +119,7 @@ func addFlags(flagSet *flag.FlagSet, nsConfig NamespaceConfig) { tlsFlagsConfig := tlsFlagsConfig(nsConfig.namespace) tlsFlagsConfig.AddFlags(flagSet) - if nsConfig.namespace != primaryStorageConfig { + if nsConfig.namespace != primaryStorageNamespace { flagSet.Bool( nsConfig.namespace+suffixEnabled, false, @@ -205,16 +196,13 @@ func addFlags(flagSet *flag.FlagSet, nsConfig NamespaceConfig) { // InitFromViper initializes Options with properties from viper func (opt *Options) InitFromViper(v *viper.Viper) { - opt.Primary.initFromViper(v) - for _, cfg := range opt.others { - cfg.initFromViper(v) - } - opt.SpanStoreWriteCacheTTL = v.GetDuration(opt.Primary.namespace + suffixSpanStoreWriteCacheTTL) - opt.Index.TagBlackList = stripWhiteSpace(v.GetString(opt.Primary.namespace + suffixIndexTagsBlacklist)) - opt.Index.TagWhiteList = stripWhiteSpace(v.GetString(opt.Primary.namespace + suffixIndexTagsWhitelist)) - opt.Index.Tags = v.GetBool(opt.Primary.namespace + suffixIndexTags) - opt.Index.Logs = v.GetBool(opt.Primary.namespace + suffixIndexLogs) - opt.Index.ProcessTags = v.GetBool(opt.Primary.namespace + suffixIndexProcessTags) + opt.NamespaceConfig.initFromViper(v) + opt.SpanStoreWriteCacheTTL = v.GetDuration(opt.NamespaceConfig.namespace + suffixSpanStoreWriteCacheTTL) + opt.Index.TagBlackList = stripWhiteSpace(v.GetString(opt.NamespaceConfig.namespace + suffixIndexTagsBlacklist)) + opt.Index.TagWhiteList = stripWhiteSpace(v.GetString(opt.NamespaceConfig.namespace + suffixIndexTagsWhitelist)) + opt.Index.Tags = v.GetBool(opt.NamespaceConfig.namespace + suffixIndexTags) + opt.Index.Logs = v.GetBool(opt.NamespaceConfig.namespace + suffixIndexLogs) + opt.Index.ProcessTags = v.GetBool(opt.NamespaceConfig.namespace + suffixIndexProcessTags) } func tlsFlagsConfig(namespace string) tlscfg.ClientFlagsConfig { @@ -225,7 +213,7 @@ func tlsFlagsConfig(namespace string) tlscfg.ClientFlagsConfig { func (cfg *NamespaceConfig) initFromViper(v *viper.Viper) { tlsFlagsConfig := tlsFlagsConfig(cfg.namespace) - if cfg.namespace != primaryStorageConfig { + if cfg.namespace != primaryStorageNamespace { cfg.Enabled = v.GetBool(cfg.namespace + suffixEnabled) } cfg.Connection.ConnectionsPerHost = v.GetInt(cfg.namespace + suffixConnPerHost) @@ -255,26 +243,8 @@ func (cfg *NamespaceConfig) initFromViper(v *viper.Viper) { cfg.Connection.TLS = tlsCfg } -// GetPrimary returns primary configuration. -func (opt *Options) GetPrimary() config.Configuration { - return opt.Primary.Configuration -} - -// Get returns auxiliary named configuration. -func (opt *Options) Get(namespace string) *config.Configuration { - nsCfg, ok := opt.others[namespace] - if !ok { - nsCfg = &NamespaceConfig{} - opt.others[namespace] = nsCfg - } - if !nsCfg.Enabled { - return nil - } - nsCfg.Configuration.ApplyDefaults(&opt.Primary.Configuration) - if len(nsCfg.Connection.Servers) == 0 { - nsCfg.Connection.Servers = opt.Primary.Connection.Servers - } - return &nsCfg.Configuration +func (opt *Options) GetConfig() config.Configuration { + return opt.NamespaceConfig.Configuration } // TagIndexBlacklist returns the list of blacklisted tags diff --git a/plugin/storage/cassandra/options_test.go b/plugin/storage/cassandra/options_test.go index 8ccd813c838..35976ebe8fb 100644 --- a/plugin/storage/cassandra/options_test.go +++ b/plugin/storage/cassandra/options_test.go @@ -6,36 +6,22 @@ package cassandra import ( "testing" - "time" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "github.com/jaegertracing/jaeger/pkg/config" ) func TestOptions(t *testing.T) { opts := NewOptions("foo") - primary := opts.GetPrimary() + primary := opts.GetConfig() assert.NotEmpty(t, primary.Schema.Keyspace) assert.NotEmpty(t, primary.Connection.Servers) assert.Equal(t, 2, primary.Connection.ConnectionsPerHost) - - aux := opts.Get("archive") - assert.Nil(t, aux) - - assert.NotNil(t, opts.others["archive"]) - opts.others["archive"].Enabled = true - aux = opts.Get("archive") - require.NotNil(t, aux) - assert.Equal(t, primary.Schema.Keyspace, aux.Schema.Keyspace) - assert.Equal(t, primary.Connection.Servers, aux.Connection.Servers) - assert.Equal(t, primary.Connection.ConnectionsPerHost, aux.Connection.ConnectionsPerHost) - assert.Equal(t, primary.Connection.ReconnectInterval, aux.Connection.ReconnectInterval) } func TestOptionsWithFlags(t *testing.T) { - opts := NewOptions("cas", "cas-aux") + opts := NewOptions("cas") v, command := config.Viperize(opts.AddFlags) command.ParseFlags([]string{ "--cas.keyspace=jaeger", @@ -56,17 +42,10 @@ func TestOptionsWithFlags(t *testing.T) { "--cas.basic.allowed-authenticators=org.apache.cassandra.auth.PasswordAuthenticator,com.datastax.bdp.cassandra.auth.DseAuthenticator", "--cas.username=username", "--cas.password=password", - // enable aux with a couple overrides - "--cas-aux.enabled=true", - "--cas-aux.keyspace=jaeger-archive", - "--cas-aux.servers=3.3.3.3, 4.4.4.4", - "--cas-aux.username=username", - "--cas-aux.password=password", - "--cas-aux.basic.allowed-authenticators=org.apache.cassandra.auth.PasswordAuthenticator,com.ericsson.bss.cassandra.ecaudit.auth.AuditAuthenticator", }) opts.InitFromViper(v) - primary := opts.GetPrimary() + primary := opts.GetConfig() assert.Equal(t, "jaeger", primary.Schema.Keyspace) assert.Equal(t, "mojave", primary.Connection.LocalDC) assert.Equal(t, []string{"1.1.1.1", "2.2.2.2"}, primary.Connection.Servers) @@ -77,20 +56,6 @@ func TestOptionsWithFlags(t *testing.T) { assert.True(t, opts.Index.Tags) assert.False(t, opts.Index.ProcessTags) assert.True(t, opts.Index.Logs) - - aux := opts.Get("cas-aux") - require.NotNil(t, aux) - assert.Equal(t, "jaeger-archive", aux.Schema.Keyspace) - assert.Equal(t, []string{"3.3.3.3", "4.4.4.4"}, aux.Connection.Servers) - assert.Equal(t, []string{"org.apache.cassandra.auth.PasswordAuthenticator", "com.ericsson.bss.cassandra.ecaudit.auth.AuditAuthenticator"}, aux.Connection.Authenticator.Basic.AllowedAuthenticators) - assert.Equal(t, 42, aux.Connection.ConnectionsPerHost) - assert.Equal(t, 42, aux.Query.MaxRetryAttempts) - assert.Equal(t, 42*time.Second, aux.Query.Timeout) - assert.Equal(t, 42*time.Second, aux.Connection.ReconnectInterval) - assert.Equal(t, 4242, aux.Connection.Port) - assert.Equal(t, "", aux.Query.Consistency, "aux storage does not inherit consistency from primary") - assert.Equal(t, 3, aux.Connection.ProtoVersion) - assert.Equal(t, 42*time.Second, aux.Connection.SocketKeepAlive) } func TestDefaultTlsHostVerify(t *testing.T) { @@ -101,7 +66,7 @@ func TestDefaultTlsHostVerify(t *testing.T) { }) opts.InitFromViper(v) - primary := opts.GetPrimary() + primary := opts.GetConfig() assert.False(t, primary.Connection.TLS.InsecureSkipVerify) } diff --git a/plugin/storage/es/factory.go b/plugin/storage/es/factory.go index e1c7ae807a4..595e81ffe29 100644 --- a/plugin/storage/es/factory.go +++ b/plugin/storage/es/factory.go @@ -43,10 +43,11 @@ const ( var ( // interface comformance checks _ storage.Factory = (*Factory)(nil) - _ storage.ArchiveFactory = (*Factory)(nil) _ io.Closer = (*Factory)(nil) _ plugin.Configurable = (*Factory)(nil) + _ storage.Inheritable = (*Factory)(nil) _ storage.Purger = (*Factory)(nil) + _ storage.ArchiveCapable = (*Factory)(nil) ) // Factory implements storage.Factory for Elasticsearch backend. @@ -59,19 +60,25 @@ type Factory struct { newClientFn func(c *config.Configuration, logger *zap.Logger, metricsFactory metrics.Factory) (es.Client, error) - primaryConfig *config.Configuration - archiveConfig *config.Configuration + config *config.Configuration - primaryClient atomic.Pointer[es.Client] - archiveClient atomic.Pointer[es.Client] + client atomic.Pointer[es.Client] - watchers []*fswatcher.FSWatcher + pwdFileWatcher *fswatcher.FSWatcher } // NewFactory creates a new Factory. func NewFactory() *Factory { return &Factory{ - Options: NewOptions(primaryNamespace, archiveNamespace), + Options: NewOptions(primaryNamespace), + newClientFn: config.NewClient, + tracer: otel.GetTracerProvider(), + } +} + +func NewArchiveFactory() *Factory { + return &Factory{ + Options: NewOptions(archiveNamespace), newClientFn: config.NewClient, tracer: otel.GetTracerProvider(), } @@ -89,20 +96,11 @@ func NewFactoryWithConfig( defaultConfig := DefaultConfig() cfg.ApplyDefaults(&defaultConfig) - archive := make(map[string]*namespaceConfig) - archive[archiveNamespace] = &namespaceConfig{ - Configuration: cfg, - namespace: archiveNamespace, + f := &Factory{ + config: &cfg, + newClientFn: config.NewClient, + tracer: otel.GetTracerProvider(), } - - f := NewFactory() - f.configureFromOptions(&Options{ - Primary: namespaceConfig{ - Configuration: cfg, - namespace: primaryNamespace, - }, - others: archive, - }) err := f.Initialize(metricsFactory, logger) if err != nil { return nil, err @@ -124,8 +122,7 @@ func (f *Factory) InitFromViper(v *viper.Viper, _ *zap.Logger) { // configureFromOptions configures factory from Options struct. func (f *Factory) configureFromOptions(o *Options) { f.Options = o - f.primaryConfig = f.Options.GetPrimary() - f.archiveConfig = f.Options.Get(archiveNamespace) + f.config = f.Options.GetConfig() } // Initialize implements storage.Factory. @@ -133,48 +130,37 @@ func (f *Factory) Initialize(metricsFactory metrics.Factory, logger *zap.Logger) f.metricsFactory = metricsFactory f.logger = logger - primaryClient, err := f.newClientFn(f.primaryConfig, logger, metricsFactory) + client, err := f.newClientFn(f.config, logger, metricsFactory) if err != nil { - return fmt.Errorf("failed to create primary Elasticsearch client: %w", err) + return fmt.Errorf("failed to create Elasticsearch client: %w", err) } - f.primaryClient.Store(&primaryClient) + f.client.Store(&client) - if f.primaryConfig.Authentication.BasicAuthentication.PasswordFilePath != "" { - primaryWatcher, err := fswatcher.New([]string{f.primaryConfig.Authentication.BasicAuthentication.PasswordFilePath}, f.onPrimaryPasswordChange, f.logger) + if f.config.Authentication.BasicAuthentication.PasswordFilePath != "" { + watcher, err := fswatcher.New([]string{f.config.Authentication.BasicAuthentication.PasswordFilePath}, f.onPasswordChange, f.logger) if err != nil { - return fmt.Errorf("failed to create watcher for primary ES client's password: %w", err) + return fmt.Errorf("failed to create watcher for ES client's password: %w", err) } - f.watchers = append(f.watchers, primaryWatcher) + f.pwdFileWatcher = watcher } - if f.archiveConfig.Enabled { - archiveClient, err := f.newClientFn(f.archiveConfig, logger, metricsFactory) - if err != nil { - return fmt.Errorf("failed to create archive Elasticsearch client: %w", err) - } - f.archiveClient.Store(&archiveClient) - - if f.archiveConfig.Authentication.BasicAuthentication.PasswordFilePath != "" { - archiveWatcher, err := fswatcher.New([]string{f.archiveConfig.Authentication.BasicAuthentication.PasswordFilePath}, f.onArchivePasswordChange, f.logger) - if err != nil { - return fmt.Errorf("failed to create watcher for archive ES client's password: %w", err) - } - f.watchers = append(f.watchers, archiveWatcher) + if f.Options != nil && f.Options.Config.namespace == archiveNamespace { + aliasSuffix := "archive" + if f.config.UseReadWriteAliases { + f.config.ReadAliasSuffix = aliasSuffix + "-read" + f.config.WriteAliasSuffix = aliasSuffix + "-write" + } else { + f.config.ReadAliasSuffix = aliasSuffix + f.config.WriteAliasSuffix = aliasSuffix } + f.config.UseReadWriteAliases = true } return nil } -func (f *Factory) getPrimaryClient() es.Client { - if c := f.primaryClient.Load(); c != nil { - return *c - } - return nil -} - -func (f *Factory) getArchiveClient() es.Client { - if c := f.archiveClient.Load(); c != nil { +func (f *Factory) getClient() es.Client { + if c := f.client.Load(); c != nil { return *c } return nil @@ -182,7 +168,7 @@ func (f *Factory) getArchiveClient() es.Client { // CreateSpanReader implements storage.Factory func (f *Factory) CreateSpanReader() (spanstore.Reader, error) { - sr, err := createSpanReader(f.getPrimaryClient, f.primaryConfig, f.logger, f.tracer, "", f.primaryConfig.UseReadWriteAliases) + sr, err := createSpanReader(f.getClient, f.config, f.logger, f.tracer, f.config.ReadAliasSuffix, f.config.UseReadWriteAliases) if err != nil { return nil, err } @@ -191,54 +177,12 @@ func (f *Factory) CreateSpanReader() (spanstore.Reader, error) { // CreateSpanWriter implements storage.Factory func (f *Factory) CreateSpanWriter() (spanstore.Writer, error) { - return createSpanWriter(f.getPrimaryClient, f.primaryConfig, f.metricsFactory, f.logger, "", f.primaryConfig.UseReadWriteAliases) + return createSpanWriter(f.getClient, f.config, f.metricsFactory, f.logger, f.config.WriteAliasSuffix, f.config.UseReadWriteAliases) } // CreateDependencyReader implements storage.Factory func (f *Factory) CreateDependencyReader() (dependencystore.Reader, error) { - return createDependencyReader(f.getPrimaryClient, f.primaryConfig, f.logger) -} - -// CreateArchiveSpanReader implements storage.ArchiveFactory -func (f *Factory) CreateArchiveSpanReader() (spanstore.Reader, error) { - if !f.archiveConfig.Enabled { - return nil, nil - } - readAliasSuffix := "archive" - if f.archiveConfig.UseReadWriteAliases { - readAliasSuffix += "-read" - } - sr, err := createSpanReader(f.getArchiveClient, f.archiveConfig, f.logger, f.tracer, readAliasSuffix, true) - if err != nil { - return nil, err - } - archiveMetricsFactory := f.metricsFactory.Namespace( - metrics.NSOptions{ - Tags: map[string]string{ - "role": "archive", - }, - }, - ) - return spanstoremetrics.NewReaderDecorator(sr, archiveMetricsFactory), nil -} - -// CreateArchiveSpanWriter implements storage.ArchiveFactory -func (f *Factory) CreateArchiveSpanWriter() (spanstore.Writer, error) { - if !f.archiveConfig.Enabled { - return nil, nil - } - writeAliasSuffix := "archive" - if f.archiveConfig.UseReadWriteAliases { - writeAliasSuffix += "-write" - } - archiveMetricsFactory := f.metricsFactory.Namespace( - metrics.NSOptions{ - Tags: map[string]string{ - "role": "archive", - }, - }, - ) - return createSpanWriter(f.getArchiveClient, f.archiveConfig, archiveMetricsFactory, f.logger, writeAliasSuffix, true) + return createDependencyReader(f.getClient, f.config, f.logger) } func createSpanReader( @@ -317,23 +261,23 @@ func createSpanWriter( func (f *Factory) CreateSamplingStore(int /* maxBuckets */) (samplingstore.Store, error) { params := esSampleStore.Params{ - Client: f.getPrimaryClient, + Client: f.getClient, Logger: f.logger, - IndexPrefix: f.primaryConfig.Indices.IndexPrefix, - IndexDateLayout: f.primaryConfig.Indices.Sampling.DateLayout, - IndexRolloverFrequency: config.RolloverFrequencyAsNegativeDuration(f.primaryConfig.Indices.Sampling.RolloverFrequency), - Lookback: f.primaryConfig.AdaptiveSamplingLookback, - MaxDocCount: f.primaryConfig.MaxDocCount, + IndexPrefix: f.config.Indices.IndexPrefix, + IndexDateLayout: f.config.Indices.Sampling.DateLayout, + IndexRolloverFrequency: config.RolloverFrequencyAsNegativeDuration(f.config.Indices.Sampling.RolloverFrequency), + Lookback: f.config.AdaptiveSamplingLookback, + MaxDocCount: f.config.MaxDocCount, } store := esSampleStore.NewSamplingStore(params) - if f.primaryConfig.CreateIndexTemplates && !f.primaryConfig.UseILM { - mappingBuilder := mappingBuilderFromConfig(f.primaryConfig) + if f.config.CreateIndexTemplates && !f.config.UseILM { + mappingBuilder := mappingBuilderFromConfig(f.config) samplingMapping, err := mappingBuilder.GetSamplingMappings() if err != nil { return nil, err } - if _, err := f.getPrimaryClient().CreateTemplate(params.PrefixedIndexName()).Body(samplingMapping).Do(context.Background()); err != nil { + if _, err := f.getClient().CreateTemplate(params.PrefixedIndexName()).Body(samplingMapping).Do(context.Background()); err != nil { return nil, fmt.Errorf("failed to create template: %w", err) } } @@ -372,30 +316,16 @@ var _ io.Closer = (*Factory)(nil) func (f *Factory) Close() error { var errs []error - for _, w := range f.watchers { - errs = append(errs, w.Close()) - } - errs = append(errs, f.getPrimaryClient().Close()) - if client := f.getArchiveClient(); client != nil { - errs = append(errs, client.Close()) + if f.pwdFileWatcher != nil { + errs = append(errs, f.pwdFileWatcher.Close()) } + errs = append(errs, f.getClient().Close()) return errors.Join(errs...) } -func (f *Factory) onPrimaryPasswordChange() { - f.onClientPasswordChange(f.primaryConfig, &f.primaryClient, f.metricsFactory) -} - -func (f *Factory) onArchivePasswordChange() { - archiveMetricsFactory := f.metricsFactory.Namespace( - metrics.NSOptions{ - Tags: map[string]string{ - "role": "archive", - }, - }, - ) - f.onClientPasswordChange(f.archiveConfig, &f.archiveClient, archiveMetricsFactory) +func (f *Factory) onPasswordChange() { + f.onClientPasswordChange(f.config, &f.client, f.metricsFactory) } func (f *Factory) onClientPasswordChange(cfg *config.Configuration, client *atomic.Pointer[es.Client], mf metrics.Factory) { @@ -422,7 +352,7 @@ func (f *Factory) onClientPasswordChange(cfg *config.Configuration, client *atom } func (f *Factory) Purge(ctx context.Context) error { - esClient := f.getPrimaryClient() + esClient := f.getClient() _, err := esClient.DeleteIndex("*").Do(ctx) return err } @@ -434,3 +364,13 @@ func loadTokenFromFile(path string) (string, error) { } return strings.TrimRight(string(b), "\r\n"), nil } + +func (f *Factory) InheritSettingsFrom(other storage.Factory) { + if otherFactory, ok := other.(*Factory); ok { + f.config.ApplyDefaults(otherFactory.config) + } +} + +func (f *Factory) IsArchiveCapable() bool { + return f.Options.Config.namespace == archiveNamespace && f.Options.Config.Enabled +} diff --git a/plugin/storage/es/factory_test.go b/plugin/storage/es/factory_test.go index 9b35119420c..07887d0587c 100644 --- a/plugin/storage/es/factory_test.go +++ b/plugin/storage/es/factory_test.go @@ -8,7 +8,6 @@ import ( "context" "encoding/base64" "errors" - "fmt" "net/http" "net/http/httptest" "os" @@ -68,16 +67,7 @@ func TestElasticsearchFactory(t *testing.T) { f.InitFromViper(v, zap.NewNop()) f.newClientFn = (&mockClientBuilder{err: errors.New("made-up error")}).NewClient - require.EqualError(t, f.Initialize(metrics.NullFactory, zap.NewNop()), "failed to create primary Elasticsearch client: made-up error") - - f.archiveConfig.Enabled = true - f.newClientFn = func(c *escfg.Configuration, logger *zap.Logger, metricsFactory metrics.Factory) (es.Client, error) { - // to test archive storage error, pretend that primary client creation is successful - // but override newClientFn so it fails for the next invocation - f.newClientFn = (&mockClientBuilder{err: errors.New("made-up error2")}).NewClient - return (&mockClientBuilder{}).NewClient(c, logger, metricsFactory) - } - require.EqualError(t, f.Initialize(metrics.NullFactory, zap.NewNop()), "failed to create archive Elasticsearch client: made-up error2") + require.EqualError(t, f.Initialize(metrics.NullFactory, zap.NewNop()), "failed to create Elasticsearch client: made-up error") f.newClientFn = (&mockClientBuilder{}).NewClient require.NoError(t, f.Initialize(metrics.NullFactory, zap.NewNop())) @@ -91,26 +81,57 @@ func TestElasticsearchFactory(t *testing.T) { _, err = f.CreateDependencyReader() require.NoError(t, err) - _, err = f.CreateArchiveSpanReader() - require.NoError(t, err) - - _, err = f.CreateArchiveSpanWriter() - require.NoError(t, err) - _, err = f.CreateSamplingStore(1) require.NoError(t, err) require.NoError(t, f.Close()) } +func TestArchiveFactory(t *testing.T) { + tests := []struct { + name string + args []string + expectedReadAlias string + expectedWriteAlias string + }{ + { + name: "default settings", + args: []string{}, + expectedReadAlias: "archive", + expectedWriteAlias: "archive", + }, + { + name: "use read write aliases", + args: []string{"--es-archive.use-aliases=true"}, + expectedReadAlias: "archive-read", + expectedWriteAlias: "archive-write", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + f := NewArchiveFactory() + v, command := config.Viperize(f.AddFlags) + command.ParseFlags(test.args) + f.InitFromViper(v, zap.NewNop()) + + f.newClientFn = (&mockClientBuilder{}).NewClient + require.NoError(t, f.Initialize(metrics.NullFactory, zap.NewNop())) + + require.Equal(t, test.expectedReadAlias, f.config.ReadAliasSuffix) + require.Equal(t, test.expectedWriteAlias, f.config.WriteAliasSuffix) + require.True(t, f.config.UseReadWriteAliases) + }) + } +} + func TestElasticsearchTagsFileDoNotExist(t *testing.T) { f := NewFactory() - f.primaryConfig = &escfg.Configuration{ + f.config = &escfg.Configuration{ Tags: escfg.TagsAsFields{ File: "fixtures/file-does-not-exist.txt", }, } - f.archiveConfig = &escfg.Configuration{} f.newClientFn = (&mockClientBuilder{}).NewClient require.NoError(t, f.Initialize(metrics.NullFactory, zap.NewNop())) defer f.Close() @@ -121,13 +142,9 @@ func TestElasticsearchTagsFileDoNotExist(t *testing.T) { func TestElasticsearchILMUsedWithoutReadWriteAliases(t *testing.T) { f := NewFactory() - f.primaryConfig = &escfg.Configuration{ + f.config = &escfg.Configuration{ UseILM: true, } - f.archiveConfig = &escfg.Configuration{ - Enabled: true, - UseILM: true, - } f.newClientFn = (&mockClientBuilder{}).NewClient require.NoError(t, f.Initialize(metrics.NullFactory, zap.NewNop())) defer f.Close() @@ -138,10 +155,6 @@ func TestElasticsearchILMUsedWithoutReadWriteAliases(t *testing.T) { r, err := f.CreateSpanReader() require.EqualError(t, err, "--es.use-ilm must always be used in conjunction with --es.use-aliases to ensure ES writers and readers refer to the single index mapping") assert.Nil(t, r) - - ar, err := f.CreateArchiveSpanReader() - require.EqualError(t, err, "--es.use-ilm must always be used in conjunction with --es.use-aliases to ensure ES writers and readers refer to the single index mapping") - assert.Nil(t, ar) } func TestTagKeysAsFields(t *testing.T) { @@ -203,8 +216,7 @@ func TestTagKeysAsFields(t *testing.T) { func TestCreateTemplateError(t *testing.T) { f := NewFactory() - f.primaryConfig = &escfg.Configuration{CreateIndexTemplates: true} - f.archiveConfig = &escfg.Configuration{} + f.config = &escfg.Configuration{CreateIndexTemplates: true} f.newClientFn = (&mockClientBuilder{createTemplateError: errors.New("template-error")}).NewClient err := f.Initialize(metrics.NullFactory, zap.NewNop()) require.NoError(t, err) @@ -221,8 +233,7 @@ func TestCreateTemplateError(t *testing.T) { func TestILMDisableTemplateCreation(t *testing.T) { f := NewFactory() - f.primaryConfig = &escfg.Configuration{UseILM: true, UseReadWriteAliases: true, CreateIndexTemplates: true} - f.archiveConfig = &escfg.Configuration{} + f.config = &escfg.Configuration{UseILM: true, UseReadWriteAliases: true, CreateIndexTemplates: true} f.newClientFn = (&mockClientBuilder{createTemplateError: errors.New("template-error")}).NewClient err := f.Initialize(metrics.NullFactory, zap.NewNop()) defer f.Close() @@ -231,57 +242,13 @@ func TestILMDisableTemplateCreation(t *testing.T) { require.NoError(t, err) // as the createTemplate is not called, CreateSpanWriter should not return an error } -func TestArchiveDisabled(t *testing.T) { - f := NewFactory() - f.archiveConfig = &escfg.Configuration{Enabled: false} - f.newClientFn = (&mockClientBuilder{}).NewClient - w, err := f.CreateArchiveSpanWriter() - assert.Nil(t, w) - require.NoError(t, err) - r, err := f.CreateArchiveSpanReader() - assert.Nil(t, r) - require.NoError(t, err) -} - -func TestArchiveEnabled(t *testing.T) { - tests := []struct { - useReadWriteAliases bool - }{ - { - useReadWriteAliases: false, - }, - { - useReadWriteAliases: true, - }, - } - for _, test := range tests { - t.Run(fmt.Sprintf("useReadWriteAliases=%v", test.useReadWriteAliases), func(t *testing.T) { - f := NewFactory() - f.primaryConfig = &escfg.Configuration{} - f.archiveConfig = &escfg.Configuration{Enabled: true, UseReadWriteAliases: test.useReadWriteAliases} - f.newClientFn = (&mockClientBuilder{}).NewClient - err := f.Initialize(metrics.NullFactory, zap.NewNop()) - require.NoError(t, err) - defer f.Close() // Ensure resources are cleaned up if initialization is successful - w, err := f.CreateArchiveSpanWriter() - require.NoError(t, err) - assert.NotNil(t, w) - r, err := f.CreateArchiveSpanReader() - require.NoError(t, err) - assert.NotNil(t, r) - }) - } -} - func TestConfigureFromOptions(t *testing.T) { f := NewFactory() o := &Options{ - Primary: namespaceConfig{Configuration: escfg.Configuration{Servers: []string{"server"}}}, - others: map[string]*namespaceConfig{"es-archive": {Configuration: escfg.Configuration{Servers: []string{"server2"}}}}, + Config: namespaceConfig{Configuration: escfg.Configuration{Servers: []string{"server"}}}, } f.configureFromOptions(o) - assert.Equal(t, o.GetPrimary(), f.primaryConfig) - assert.Equal(t, o.Get(archiveNamespace), f.archiveConfig) + assert.Equal(t, o.GetConfig(), f.config) } func TestESStorageFactoryWithConfig(t *testing.T) { @@ -339,19 +306,14 @@ func TestESStorageFactoryWithConfigError(t *testing.T) { LogLevel: "error", } _, err := NewFactoryWithConfig(cfg, metrics.NullFactory, zap.NewNop()) - require.ErrorContains(t, err, "failed to create primary Elasticsearch client") + require.ErrorContains(t, err, "failed to create Elasticsearch client") } func TestPasswordFromFile(t *testing.T) { defer testutils.VerifyGoLeaksOnce(t) t.Run("primary client", func(t *testing.T) { f := NewFactory() - testPasswordFromFile(t, f, f.getPrimaryClient, f.CreateSpanWriter) - }) - - t.Run("archive client", func(t *testing.T) { - f2 := NewFactory() - testPasswordFromFile(t, f2, f2.getArchiveClient, f2.CreateArchiveSpanWriter) + testPasswordFromFile(t, f, f.getClient, f.CreateSpanWriter) }) t.Run("load token error", func(t *testing.T) { @@ -391,21 +353,7 @@ func testPasswordFromFile(t *testing.T, f *Factory, getClient func() es.Client, pwdFile := filepath.Join(t.TempDir(), "pwd") require.NoError(t, os.WriteFile(pwdFile, []byte(pwd1), 0o600)) - f.primaryConfig = &escfg.Configuration{ - Servers: []string{server.URL}, - LogLevel: "debug", - Authentication: escfg.Authentication{ - BasicAuthentication: escfg.BasicAuthentication{ - Username: "user", - PasswordFilePath: pwdFile, - }, - }, - BulkProcessing: escfg.BulkProcessing{ - MaxBytes: -1, // disable bulk; we want immediate flush - }, - } - f.archiveConfig = &escfg.Configuration{ - Enabled: true, + f.config = &escfg.Configuration{ Servers: []string{server.URL}, LogLevel: "debug", Authentication: escfg.Authentication{ @@ -464,8 +412,7 @@ func testPasswordFromFile(t *testing.T, f *Factory, getClient func() es.Client, func TestFactoryESClientsAreNil(t *testing.T) { f := &Factory{} - assert.Nil(t, f.getPrimaryClient()) - assert.Nil(t, f.getArchiveClient()) + assert.Nil(t, f.getClient()) } func TestPasswordFromFileErrors(t *testing.T) { @@ -479,16 +426,7 @@ func TestPasswordFromFileErrors(t *testing.T) { require.NoError(t, os.WriteFile(pwdFile, []byte("first password"), 0o600)) f := NewFactory() - f.primaryConfig = &escfg.Configuration{ - Servers: []string{server.URL}, - LogLevel: "debug", - Authentication: escfg.Authentication{ - BasicAuthentication: escfg.BasicAuthentication{ - PasswordFilePath: pwdFile, - }, - }, - } - f.archiveConfig = &escfg.Configuration{ + f.config = &escfg.Configuration{ Servers: []string{server.URL}, LogLevel: "debug", Authentication: escfg.Authentication{ @@ -502,16 +440,72 @@ func TestPasswordFromFileErrors(t *testing.T) { require.NoError(t, f.Initialize(metrics.NullFactory, logger)) defer f.Close() - f.primaryConfig.Servers = []string{} - f.onPrimaryPasswordChange() - assert.Contains(t, buf.String(), "no servers specified") - - f.archiveConfig.Servers = []string{} - buf.Reset() - f.onArchivePasswordChange() + f.config.Servers = []string{} + f.onPasswordChange() assert.Contains(t, buf.String(), "no servers specified") require.NoError(t, os.Remove(pwdFile)) - f.onPrimaryPasswordChange() - f.onArchivePasswordChange() + f.onPasswordChange() +} + +func TestInheritSettingsFrom(t *testing.T) { + primaryFactory := NewFactory() + primaryFactory.config = &escfg.Configuration{ + MaxDocCount: 99, + } + + archiveFactory := NewArchiveFactory() + archiveFactory.config = &escfg.Configuration{ + SendGetBodyAs: "PUT", + } + + archiveFactory.InheritSettingsFrom(primaryFactory) + + require.Equal(t, "PUT", archiveFactory.config.SendGetBodyAs) + require.Equal(t, 99, primaryFactory.config.MaxDocCount) +} + +func TestIsArchiveCapable(t *testing.T) { + tests := []struct { + name string + namespace string + enabled bool + expected bool + }{ + { + name: "archive capable", + namespace: "es-archive", + enabled: true, + expected: true, + }, + { + name: "not capable", + namespace: "es-archive", + enabled: false, + expected: false, + }, + { + name: "capable + wrong namespace", + namespace: "es", + enabled: true, + expected: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + factory := &Factory{ + Options: &Options{ + Config: namespaceConfig{ + namespace: test.namespace, + Configuration: escfg.Configuration{ + Enabled: test.enabled, + }, + }, + }, + } + result := factory.IsArchiveCapable() + require.Equal(t, test.expected, result) + }) + } } diff --git a/plugin/storage/es/options.go b/plugin/storage/es/options.go index ee52d718032..264bf3393fa 100644 --- a/plugin/storage/es/options.go +++ b/plugin/storage/es/options.go @@ -84,9 +84,8 @@ var defaultIndexOptions = config.IndexOptions{ // to bind them to command line flag and apply overlays, so that some configurations // (e.g. archive) may be underspecified and infer the rest of its parameters from primary. type Options struct { - Primary namespaceConfig `mapstructure:",squash"` - - others map[string]*namespaceConfig + // TODO: remove indirection + Config namespaceConfig `mapstructure:",squash"` } type namespaceConfig struct { @@ -95,24 +94,14 @@ type namespaceConfig struct { } // NewOptions creates a new Options struct. -func NewOptions(primaryNamespace string, otherNamespaces ...string) *Options { +func NewOptions(namespace string) *Options { // TODO all default values should be defined via cobra flags defaultConfig := DefaultConfig() options := &Options{ - Primary: namespaceConfig{ - Configuration: defaultConfig, - namespace: primaryNamespace, - }, - others: make(map[string]*namespaceConfig, len(otherNamespaces)), - } - - // Other namespaces need to be explicitly enabled. - defaultConfig.Enabled = false - for _, namespace := range otherNamespaces { - options.others[namespace] = &namespaceConfig{ + Config: namespaceConfig{ Configuration: defaultConfig, namespace: namespace, - } + }, } return options @@ -126,10 +115,7 @@ func (cfg *namespaceConfig) getTLSFlagsConfig() tlscfg.ClientFlagsConfig { // AddFlags adds flags for Options func (opt *Options) AddFlags(flagSet *flag.FlagSet) { - addFlags(flagSet, &opt.Primary) - for _, cfg := range opt.others { - addFlags(flagSet, cfg) - } + addFlags(flagSet, &opt.Config) } func addFlags(flagSet *flag.FlagSet, nsConfig *namespaceConfig) { @@ -289,7 +275,7 @@ func addFlags(flagSet *flag.FlagSet, nsConfig *namespaceConfig) { if nsConfig.namespace == archiveNamespace { flagSet.Bool( nsConfig.namespace+suffixEnabled, - nsConfig.Enabled, + false, "Enable extra storage") } else { // MaxSpanAge is only relevant when searching for unarchived traces. @@ -304,10 +290,7 @@ func addFlags(flagSet *flag.FlagSet, nsConfig *namespaceConfig) { // InitFromViper initializes Options with properties from viper func (opt *Options) InitFromViper(v *viper.Viper) { - initFromViper(&opt.Primary, v) - for _, cfg := range opt.others { - initFromViper(cfg, v) - } + initFromViper(&opt.Config, v) } func initFromViper(cfg *namespaceConfig, v *viper.Viper) { @@ -388,22 +371,8 @@ func initFromViper(cfg *namespaceConfig, v *viper.Viper) { } // GetPrimary returns primary configuration. -func (opt *Options) GetPrimary() *config.Configuration { - return &opt.Primary.Configuration -} - -// Get returns auxiliary named configuration. -func (opt *Options) Get(namespace string) *config.Configuration { - nsCfg, ok := opt.others[namespace] - if !ok { - nsCfg = &namespaceConfig{} - opt.others[namespace] = nsCfg - } - nsCfg.Configuration.ApplyDefaults(&opt.Primary.Configuration) - if len(nsCfg.Configuration.Servers) == 0 { - nsCfg.Servers = opt.Primary.Servers - } - return &nsCfg.Configuration +func (opt *Options) GetConfig() *config.Configuration { + return &opt.Config.Configuration } // stripWhiteSpace removes all whitespace characters from a string diff --git a/plugin/storage/es/options_test.go b/plugin/storage/es/options_test.go index 87b2ce11d7f..cb3b05af479 100644 --- a/plugin/storage/es/options_test.go +++ b/plugin/storage/es/options_test.go @@ -5,7 +5,6 @@ package es import ( - "net/http" "testing" "time" @@ -18,7 +17,7 @@ import ( func TestOptions(t *testing.T) { opts := NewOptions("foo") - primary := opts.GetPrimary() + primary := opts.GetConfig() assert.Empty(t, primary.Authentication.BasicAuthentication.Username) assert.Empty(t, primary.Authentication.BasicAuthentication.Password) assert.Empty(t, primary.Authentication.BasicAuthentication.PasswordFilePath) @@ -35,16 +34,10 @@ func TestOptions(t *testing.T) { assert.Equal(t, 72*time.Hour, primary.MaxSpanAge) assert.False(t, primary.Sniffing.Enabled) assert.False(t, primary.Sniffing.UseHTTPS) - - aux := opts.Get("archive") - assert.Equal(t, primary.Authentication.BasicAuthentication.Username, aux.Authentication.BasicAuthentication.Username) - assert.Equal(t, primary.Authentication.BasicAuthentication.Password, aux.Authentication.BasicAuthentication.Password) - assert.Equal(t, primary.Authentication.BasicAuthentication.PasswordFilePath, aux.Authentication.BasicAuthentication.PasswordFilePath) - assert.Equal(t, primary.Servers, aux.Servers) } func TestOptionsWithFlags(t *testing.T) { - opts := NewOptions("es", "es.aux") + opts := NewOptions("es") v, command := config.Viperize(opts.AddFlags) err := command.ParseFlags([]string{ "--es.server-urls=1.1.1.1, 2.2.2.2", @@ -62,11 +55,6 @@ func TestOptionsWithFlags(t *testing.T) { "--es.index-rollover-frequency-services=day", // a couple overrides "--es.remote-read-clusters=cluster_one,cluster_two", - "--es.aux.server-urls=3.3.3.3, 4.4.4.4", - "--es.aux.max-span-age=24h", - "--es.aux.num-replicas=10", - "--es.aux.index-date-separator=.", - "--es.aux.index-rollover-frequency-spans=hour", "--es.tls.enabled=true", "--es.tls.skip-host-verify=true", "--es.tags-as-fields.all=true", @@ -79,7 +67,7 @@ func TestOptionsWithFlags(t *testing.T) { require.NoError(t, err) opts.InitFromViper(v) - primary := opts.GetPrimary() + primary := opts.GetConfig() assert.Equal(t, "hello", primary.Authentication.BasicAuthentication.Username) assert.Equal(t, "world", primary.Authentication.BasicAuthentication.Password) assert.Equal(t, "/foo/bar", primary.Authentication.BearerTokenAuthentication.FilePath) @@ -97,32 +85,11 @@ func TestOptionsWithFlags(t *testing.T) { assert.Equal(t, "test,tags", primary.Tags.Include) assert.Equal(t, "20060102", primary.Indices.Services.DateLayout) assert.Equal(t, "2006010215", primary.Indices.Spans.DateLayout) - aux := opts.Get("es.aux") - assert.Equal(t, []string{"3.3.3.3", "4.4.4.4"}, aux.Servers) - assert.Equal(t, "hello", aux.Authentication.BasicAuthentication.Username) - assert.Equal(t, "world", aux.Authentication.BasicAuthentication.Password) - assert.EqualValues(t, 5, aux.Indices.Spans.Shards) - assert.EqualValues(t, 5, aux.Indices.Services.Shards) - assert.EqualValues(t, 5, aux.Indices.Sampling.Shards) - assert.EqualValues(t, 5, aux.Indices.Dependencies.Shards) - assert.EqualValues(t, 10, aux.Indices.Spans.Replicas) - assert.EqualValues(t, 10, aux.Indices.Services.Replicas) - assert.EqualValues(t, 10, aux.Indices.Sampling.Replicas) - assert.EqualValues(t, 10, aux.Indices.Dependencies.Replicas) - assert.Equal(t, 24*time.Hour, aux.MaxSpanAge) - assert.True(t, aux.Sniffing.Enabled) - assert.True(t, aux.Tags.AllAsFields) - assert.Equal(t, "@", aux.Tags.DotReplacement) - assert.Equal(t, "./file.txt", aux.Tags.File) - assert.Equal(t, "test,tags", aux.Tags.Include) - assert.Equal(t, "2006.01.02", aux.Indices.Services.DateLayout) - assert.Equal(t, "2006.01.02.15", aux.Indices.Spans.DateLayout) assert.True(t, primary.UseILM) - assert.Equal(t, http.MethodPost, aux.SendGetBodyAs) } func TestEmptyRemoteReadClusters(t *testing.T) { - opts := NewOptions("es", "es.aux") + opts := NewOptions("es") v, command := config.Viperize(opts.AddFlags) err := command.ParseFlags([]string{ "--es.remote-read-clusters=", @@ -130,12 +97,12 @@ func TestEmptyRemoteReadClusters(t *testing.T) { require.NoError(t, err) opts.InitFromViper(v) - primary := opts.GetPrimary() + primary := opts.GetConfig() assert.Equal(t, []string{}, primary.RemoteReadClusters) } func TestMaxSpanAgeSetErrorInArchiveMode(t *testing.T) { - opts := NewOptions("es", archiveNamespace) + opts := NewOptions(archiveNamespace) _, command := config.Viperize(opts.AddFlags) flags := []string{"--es-archive.max-span-age=24h"} err := command.ParseFlags(flags) @@ -153,12 +120,12 @@ func TestMaxDocCount(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - opts := NewOptions("es", "es.aux") + opts := NewOptions("es") v, command := config.Viperize(opts.AddFlags) command.ParseFlags(tc.flags) opts.InitFromViper(v) - primary := opts.GetPrimary() + primary := opts.GetConfig() assert.Equal(t, tc.wantMaxDocCount, primary.MaxDocCount) }) } @@ -184,7 +151,7 @@ func TestIndexDateSeparator(t *testing.T) { command.ParseFlags(tc.flags) opts.InitFromViper(v) - primary := opts.GetPrimary() + primary := opts.GetConfig() assert.Equal(t, tc.wantDateLayout, primary.Indices.Spans.DateLayout) }) } @@ -238,7 +205,7 @@ func TestIndexRollover(t *testing.T) { v, command := config.Viperize(opts.AddFlags) command.ParseFlags(tc.flags) opts.InitFromViper(v) - primary := opts.GetPrimary() + primary := opts.GetConfig() assert.Equal(t, tc.wantSpanDateLayout, primary.Indices.Spans.DateLayout) assert.Equal(t, tc.wantServiceDateLayout, primary.Indices.Services.DateLayout) assert.Equal(t, tc.wantSpanIndexRolloverFrequency, escfg.RolloverFrequencyAsNegativeDuration(primary.Indices.Spans.RolloverFrequency)) diff --git a/plugin/storage/factory.go b/plugin/storage/factory.go index be1b808c46e..6142c299456 100644 --- a/plugin/storage/factory.go +++ b/plugin/storage/factory.go @@ -74,10 +74,9 @@ func AllSamplingStorageTypes() []string { } var ( // interface comformance checks - _ storage.Factory = (*Factory)(nil) - _ storage.ArchiveFactory = (*Factory)(nil) - _ io.Closer = (*Factory)(nil) - _ plugin.Configurable = (*Factory)(nil) + _ storage.Factory = (*Factory)(nil) + _ io.Closer = (*Factory)(nil) + _ plugin.Configurable = (*Factory)(nil) ) // Factory implements storage.Factory interface as a meta-factory for storage components. @@ -85,6 +84,7 @@ type Factory struct { FactoryConfig metricsFactory metrics.Factory factories map[string]storage.Factory + archiveFactories map[string]storage.Factory downsamplingFlagsAdded bool } @@ -103,12 +103,17 @@ func NewFactory(config FactoryConfig) (*Factory, error) { uniqueTypes[f.SamplingStorageType] = struct{}{} } f.factories = make(map[string]storage.Factory) + f.archiveFactories = make(map[string]storage.Factory) for t := range uniqueTypes { ff, err := f.getFactoryOfType(t) if err != nil { return nil, err } f.factories[t] = ff + + if af, ok := f.getArchiveFactoryOfType(t); ok { + f.archiveFactories[t] = af + } } return f, nil } @@ -134,23 +139,50 @@ func (*Factory) getFactoryOfType(factoryType string) (storage.Factory, error) { } } -// Initialize implements storage.Factory. +func (*Factory) getArchiveFactoryOfType(factoryType string) (storage.Factory, bool) { + switch factoryType { + case cassandraStorageType: + return cassandra.NewArchiveFactory(), true + case elasticsearchStorageType, opensearchStorageType: + return es.NewArchiveFactory(), true + case grpcStorageType: + return grpc.NewArchiveFactory(), true + default: + return nil, false + } +} + func (f *Factory) Initialize(metricsFactory metrics.Factory, logger *zap.Logger) error { f.metricsFactory = metricsFactory - for kind, factory := range f.factories { + + initializeFactory := func(kind string, factory storage.Factory, role string) error { mf := metricsFactory.Namespace(metrics.NSOptions{ Name: "storage", Tags: map[string]string{ "kind": kind, - "role": "primary", // can be overiden in the storage factory for archive/sampling stores + "role": role, }, }) - if err := factory.Initialize(mf, logger); err != nil { + return factory.Initialize(mf, logger) + } + + for kind, factory := range f.factories { + if err := initializeFactory(kind, factory, "primary"); err != nil { return err } } - f.publishOpts() + for kind, factory := range f.archiveFactories { + if archivable, ok := factory.(storage.ArchiveCapable); ok && archivable.IsArchiveCapable() { + if err := initializeFactory(kind, factory, "archive"); err != nil { + return err + } + } else { + delete(f.archiveFactories, kind) + } + } + + f.publishOpts() return nil } @@ -233,11 +265,15 @@ func (f *Factory) CreateDependencyReader() (dependencystore.Reader, error) { // AddFlags implements plugin.Configurable func (f *Factory) AddFlags(flagSet *flag.FlagSet) { - for _, factory := range f.factories { - if conf, ok := factory.(plugin.Configurable); ok { - conf.AddFlags(flagSet) + addFlags := func(factories map[string]storage.Factory) { + for _, factory := range factories { + if conf, ok := factory.(plugin.Configurable); ok { + conf.AddFlags(flagSet) + } } } + addFlags(f.factories) + addFlags(f.archiveFactories) } // AddPipelineFlags adds all the standard flags as well as the downsampling @@ -265,11 +301,23 @@ func (f *Factory) addDownsamplingFlags(flagSet *flag.FlagSet) { // InitFromViper implements plugin.Configurable func (f *Factory) InitFromViper(v *viper.Viper, logger *zap.Logger) { - for _, factory := range f.factories { + initializeConfigurable := func(factory storage.Factory) { if conf, ok := factory.(plugin.Configurable); ok { conf.InitFromViper(v, logger) } } + for _, factory := range f.factories { + initializeConfigurable(factory) + } + for kind, factory := range f.archiveFactories { + initializeConfigurable(factory) + + if primaryFactory, ok := f.factories[kind]; ok { + if inheritable, ok := factory.(storage.Inheritable); ok { + inheritable.InheritSettingsFrom(primaryFactory) + } + } + } f.initDownsamplingFromViper(v) } @@ -290,30 +338,37 @@ func (f *Factory) initDownsamplingFromViper(v *viper.Viper) { f.FactoryConfig.DownsamplingHashSalt = v.GetString(downsamplingHashSalt) } -// CreateArchiveSpanReader implements storage.ArchiveFactory -func (f *Factory) CreateArchiveSpanReader() (spanstore.Reader, error) { - factory, ok := f.factories[f.SpanReaderType] +func (f *Factory) createArchiveSpanReader() (spanstore.Reader, error) { + factory, ok := f.archiveFactories[f.SpanReaderType] if !ok { return nil, fmt.Errorf("no %s backend registered for span store", f.SpanReaderType) } - archive, ok := factory.(storage.ArchiveFactory) - if !ok { - return nil, storage.ErrArchiveStorageNotSupported - } - return archive.CreateArchiveSpanReader() + return factory.CreateSpanReader() } -// CreateArchiveSpanWriter implements storage.ArchiveFactory -func (f *Factory) CreateArchiveSpanWriter() (spanstore.Writer, error) { - factory, ok := f.factories[f.SpanWriterTypes[0]] +func (f *Factory) createArchiveSpanWriter() (spanstore.Writer, error) { + factory, ok := f.archiveFactories[f.SpanWriterTypes[0]] if !ok { return nil, fmt.Errorf("no %s backend registered for span store", f.SpanWriterTypes[0]) } - archive, ok := factory.(storage.ArchiveFactory) - if !ok { - return nil, storage.ErrArchiveStorageNotSupported + return factory.CreateSpanWriter() +} + +func (f *Factory) InitArchiveStorage( + logger *zap.Logger, +) (spanstore.Reader, spanstore.Writer) { + reader, err := f.createArchiveSpanReader() + if err != nil { + logger.Error("Cannot init archive storage reader", zap.Error(err)) + return nil, nil + } + writer, err := f.createArchiveSpanWriter() + if err != nil { + logger.Error("Cannot init archive storage writer", zap.Error(err)) + return nil, nil } - return archive.CreateArchiveSpanWriter() + + return reader, writer } var _ io.Closer = (*Factory)(nil) @@ -321,14 +376,19 @@ var _ io.Closer = (*Factory)(nil) // Close closes the resources held by the factory func (f *Factory) Close() error { var errs []error + closeFactory := func(factory storage.Factory) { + if closer, ok := factory.(io.Closer); ok { + if err := closer.Close(); err != nil { + errs = append(errs, err) + } + } + } for _, storageType := range f.SpanWriterTypes { if factory, ok := f.factories[storageType]; ok { - if closer, ok := factory.(io.Closer); ok { - err := closer.Close() - if err != nil { - errs = append(errs, err) - } - } + closeFactory(factory) + } + if factory, ok := f.archiveFactories[storageType]; ok { + closeFactory(factory) } } return errors.Join(errs...) diff --git a/plugin/storage/factory_test.go b/plugin/storage/factory_test.go index c09f8e7504c..a9aaa4490d3 100644 --- a/plugin/storage/factory_test.go +++ b/plugin/storage/factory_test.go @@ -20,6 +20,7 @@ import ( "github.com/jaegertracing/jaeger/pkg/config" "github.com/jaegertracing/jaeger/pkg/metrics" + "github.com/jaegertracing/jaeger/pkg/testutils" "github.com/jaegertracing/jaeger/storage" "github.com/jaegertracing/jaeger/storage/dependencystore" depStoreMocks "github.com/jaegertracing/jaeger/storage/dependencystore/mocks" @@ -90,6 +91,7 @@ func TestInitialize(t *testing.T) { mock := new(mocks.Factory) f.factories[cassandraStorageType] = mock + f.archiveFactories[cassandraStorageType] = mock m := metrics.NullFactory l := zap.NewNop() @@ -131,12 +133,6 @@ func TestCreate(t *testing.T) { assert.Equal(t, depReader, d) require.EqualError(t, err, "dep-reader-error") - _, err = f.CreateArchiveSpanReader() - require.EqualError(t, err, "archive storage not supported") - - _, err = f.CreateArchiveSpanWriter() - require.EqualError(t, err, "archive storage not supported") - mock.On("CreateSpanWriter").Return(spanWriter, nil) m := metrics.NullFactory l := zap.NewNop() @@ -190,7 +186,9 @@ func TestCreateMulti(t *testing.T) { mock := new(mocks.Factory) mock2 := new(mocks.Factory) f.factories[cassandraStorageType] = mock + f.archiveFactories[cassandraStorageType] = mock f.factories[elasticsearchStorageType] = mock2 + f.archiveFactories[elasticsearchStorageType] = mock2 spanWriter := new(spanStoreMocks.Writer) spanWriter2 := new(spanStoreMocks.Writer) @@ -213,32 +211,6 @@ func TestCreateMulti(t *testing.T) { assert.Equal(t, spanstore.NewCompositeWriter(spanWriter, spanWriter2), w) } -func TestCreateArchive(t *testing.T) { - f, err := NewFactory(defaultCfg()) - require.NoError(t, err) - assert.NotEmpty(t, f.factories[cassandraStorageType]) - - mock := &struct { - mocks.Factory - mocks.ArchiveFactory - }{} - f.factories[cassandraStorageType] = mock - - archiveSpanReader := new(spanStoreMocks.Reader) - archiveSpanWriter := new(spanStoreMocks.Writer) - - mock.ArchiveFactory.On("CreateArchiveSpanReader").Return(archiveSpanReader, errors.New("archive-span-reader-error")) - mock.ArchiveFactory.On("CreateArchiveSpanWriter").Return(archiveSpanWriter, errors.New("archive-span-writer-error")) - - ar, err := f.CreateArchiveSpanReader() - assert.Equal(t, archiveSpanReader, ar) - require.EqualError(t, err, "archive-span-reader-error") - - aw, err := f.CreateArchiveSpanWriter() - assert.Equal(t, archiveSpanWriter, aw) - require.EqualError(t, err, "archive-span-writer-error") -} - func TestCreateError(t *testing.T) { f, err := NewFactory(defaultCfg()) require.NoError(t, err) @@ -265,18 +237,6 @@ func TestCreateError(t *testing.T) { assert.Nil(t, d) require.EqualError(t, err, expectedErr) } - - { - r, err := f.CreateArchiveSpanReader() - assert.Nil(t, r) - require.EqualError(t, err, expectedErr) - } - - { - w, err := f.CreateArchiveSpanWriter() - assert.Nil(t, w) - require.EqualError(t, err, expectedErr) - } } func TestAllSamplingStorageTypes(t *testing.T) { @@ -355,6 +315,107 @@ func TestConfigurable(t *testing.T) { assert.Equal(t, v, mock.viper) } +type inheritable struct { + mocks.Factory + calledWith storage.Factory +} + +func (i *inheritable) InheritSettingsFrom(other storage.Factory) { + i.calledWith = other +} + +func TestInheritable(t *testing.T) { + f, err := NewFactory(defaultCfg()) + require.NoError(t, err) + assert.NotEmpty(t, f.factories) + assert.NotEmpty(t, f.factories[cassandraStorageType]) + assert.NotEmpty(t, f.archiveFactories[cassandraStorageType]) + + mockInheritable := new(inheritable) + f.factories[cassandraStorageType] = &mocks.Factory{} + f.archiveFactories[cassandraStorageType] = mockInheritable + + fs := new(flag.FlagSet) + v := viper.New() + + f.AddFlags(fs) + f.InitFromViper(v, zap.NewNop()) + + assert.Equal(t, f.factories[cassandraStorageType], mockInheritable.calledWith) +} + +type archiveConfigurable struct { + isConfigurable bool + *mocks.Factory +} + +func (ac *archiveConfigurable) IsArchiveCapable() bool { + return ac.isConfigurable +} + +func TestArchiveConfigurable(t *testing.T) { + tests := []struct { + name string + isArchiveCapable bool + archiveInitError error + expectedError error + expectedArchiveSize int + }{ + { + name: "Archive factory initializes successfully", + isArchiveCapable: true, + archiveInitError: nil, + expectedError: nil, + expectedArchiveSize: 1, + }, + { + name: "Archive factory initialization fails", + isArchiveCapable: true, + archiveInitError: assert.AnError, + expectedError: assert.AnError, + expectedArchiveSize: 1, + }, + { + name: "Archive factory is not archive-capable", + isArchiveCapable: false, + archiveInitError: nil, + expectedError: nil, + expectedArchiveSize: 0, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + f, err := NewFactory(defaultCfg()) + require.NoError(t, err) + + primaryFactory := &mocks.Factory{} + archiveFactory := &mocks.Factory{} + archiveConfigurable := &archiveConfigurable{ + isConfigurable: test.isArchiveCapable, + Factory: archiveFactory, + } + + f.factories[cassandraStorageType] = primaryFactory + f.archiveFactories[cassandraStorageType] = archiveConfigurable + + m := metrics.NullFactory + l := zap.NewNop() + primaryFactory.On("Initialize", m, l).Return(nil).Once() + archiveFactory.On("Initialize", m, l).Return(test.archiveInitError).Once() + + err = f.Initialize(m, l) + if test.expectedError != nil { + require.ErrorIs(t, err, test.expectedError) + } else { + require.NoError(t, err) + } + + assert.Len(t, f.archiveFactories, test.expectedArchiveSize) + }) + } +} + func TestParsingDownsamplingRatio(t *testing.T) { f := Factory{} v, command := config.Viperize(f.AddPipelineFlags) @@ -401,6 +462,69 @@ func TestPublishOpts(t *testing.T) { assert.EqualValues(t, 1, expvar.Get(spanStorageType+"-"+f.SpanReaderType).(*expvar.Int).Value()) } +func TestInitArchiveStorage(t *testing.T) { + tests := []struct { + name string + setupMock func(*mocks.Factory) + expectedReader spanstore.Reader + expectedWriter spanstore.Writer + expectedLog string + }{ + { + name: "successful initialization", + setupMock: func(mock *mocks.Factory) { + spanReader := &spanStoreMocks.Reader{} + spanWriter := &spanStoreMocks.Writer{} + mock.On("CreateSpanReader").Return(spanReader, nil) + mock.On("CreateSpanWriter").Return(spanWriter, nil) + }, + expectedReader: &spanStoreMocks.Reader{}, + expectedWriter: &spanStoreMocks.Writer{}, + }, + { + name: "error initializing reader", + setupMock: func(mock *mocks.Factory) { + mock.On("CreateSpanReader").Return(nil, assert.AnError) + }, + expectedReader: nil, + expectedWriter: nil, + expectedLog: "Cannot init archive storage reader", + }, + { + name: "error initializing writer", + setupMock: func(mock *mocks.Factory) { + spanReader := new(spanStoreMocks.Reader) + mock.On("CreateSpanReader").Return(spanReader, nil) + mock.On("CreateSpanWriter").Return(nil, assert.AnError) + }, + expectedReader: nil, + expectedWriter: nil, + expectedLog: "Cannot init archive storage writer", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + logger, buf := testutils.NewLogger() + + f, err := NewFactory(defaultCfg()) + require.NoError(t, err) + assert.NotEmpty(t, f.factories) + assert.NotEmpty(t, f.factories[cassandraStorageType]) + + mock := new(mocks.Factory) + f.archiveFactories[cassandraStorageType] = mock + test.setupMock(mock) + + reader, writer := f.InitArchiveStorage(logger) + assert.Equal(t, test.expectedReader, reader) + assert.Equal(t, test.expectedWriter, writer) + + require.Contains(t, buf.String(), test.expectedLog) + }) + } +} + type errorFactory struct { closeErr error } diff --git a/plugin/storage/grpc/config.go b/plugin/storage/grpc/config.go index 37a5d293fa2..d9150bcba33 100644 --- a/plugin/storage/grpc/config.go +++ b/plugin/storage/grpc/config.go @@ -16,6 +16,7 @@ type Config struct { Tenancy tenancy.Options `mapstructure:"multi_tenancy"` configgrpc.ClientConfig `mapstructure:",squash"` exporterhelper.TimeoutConfig `mapstructure:",squash"` + enabled bool } func DefaultConfig() Config { diff --git a/plugin/storage/grpc/factory.go b/plugin/storage/grpc/factory.go index e316ceb7d3e..dd6c68a9a40 100644 --- a/plugin/storage/grpc/factory.go +++ b/plugin/storage/grpc/factory.go @@ -35,14 +35,14 @@ import ( var ( // interface comformance checks _ storage.Factory = (*Factory)(nil) - _ storage.ArchiveFactory = (*Factory)(nil) _ io.Closer = (*Factory)(nil) _ plugin.Configurable = (*Factory)(nil) + _ storage.ArchiveCapable = (*Factory)(nil) ) // Factory implements storage.Factory and creates storage components backed by a storage plugin. type Factory struct { - config Config + options *options telset telemetry.Settings services *ClientPluginServices tracedRemoteConn *grpc.ClientConn @@ -52,7 +52,15 @@ type Factory struct { // NewFactory creates a new Factory. func NewFactory() *Factory { return &Factory{ - telset: telemetry.NoopSettings(), + options: newOptions(remotePrefix), + telset: telemetry.NoopSettings(), + } +} + +func NewArchiveFactory() *Factory { + return &Factory{ + options: newOptions(archiveRemotePrefix), + telset: telemetry.NoopSettings(), } } @@ -62,7 +70,7 @@ func NewFactoryWithConfig( telset telemetry.Settings, ) (*Factory, error) { f := NewFactory() - f.config = cfg + f.options.Config = cfg f.telset = telset if err := f.Initialize(telset.Metrics, telset.Logger); err != nil { return nil, err @@ -71,13 +79,13 @@ func NewFactoryWithConfig( } // AddFlags implements plugin.Configurable -func (*Factory) AddFlags(flagSet *flag.FlagSet) { - addFlags(flagSet) +func (f *Factory) AddFlags(flagSet *flag.FlagSet) { + f.options.addFlags(flagSet) } // InitFromViper implements plugin.Configurable func (f *Factory) InitFromViper(v *viper.Viper, logger *zap.Logger) { - if err := initFromViper(&f.config, v); err != nil { + if err := f.options.initFromViper(&f.options.Config, v); err != nil { logger.Fatal("unable to initialize gRPC storage factory", zap.Error(err)) } } @@ -95,7 +103,7 @@ func (f *Factory) Initialize(metricsFactory metrics.Factory, logger *zap.Logger) for _, opt := range opts { clientOpts = append(clientOpts, configgrpc.WithGrpcDialOption(opt)) } - return f.config.ToClientConn(context.Background(), f.telset.Host, telset, clientOpts...) + return f.options.Config.ToClientConn(context.Background(), f.telset.Host, telset, clientOpts...) } var err error @@ -103,7 +111,7 @@ func (f *Factory) Initialize(metricsFactory metrics.Factory, logger *zap.Logger) if err != nil { return fmt.Errorf("grpc storage builder failed to create a store: %w", err) } - logger.Info("Remote storage configuration", zap.Any("configuration", f.config)) + logger.Info("Remote storage configuration", zap.Any("configuration", f.options.Config)) return nil } @@ -114,7 +122,7 @@ func (f *Factory) newRemoteStorage( untracedTelset component.TelemetrySettings, newClient newClientFn, ) (*ClientPluginServices, error) { - c := f.config + c := f.options.Config if c.Auth != nil { return nil, errors.New("authenticator is not supported") } @@ -158,7 +166,6 @@ func (f *Factory) newRemoteStorage( return &ClientPluginServices{ PluginServices: shared.PluginServices{ Store: grpcClient, - ArchiveStore: grpcClient, StreamingSpanWriter: grpcClient, }, Capabilities: grpcClient, @@ -185,43 +192,6 @@ func (f *Factory) CreateDependencyReader() (dependencystore.Reader, error) { return f.services.Store.DependencyReader(), nil } -// CreateArchiveSpanReader implements storage.ArchiveFactory -func (f *Factory) CreateArchiveSpanReader() (spanstore.Reader, error) { - if f.services.Capabilities == nil { - return nil, storage.ErrArchiveStorageNotSupported - } - capabilities, err := f.services.Capabilities.Capabilities() - if err != nil { - return nil, err - } - if capabilities == nil || !capabilities.ArchiveSpanReader { - return nil, storage.ErrArchiveStorageNotSupported - } - archiveMetricsFactory := f.telset.Metrics.Namespace( - metrics.NSOptions{ - Tags: map[string]string{ - "role": "archive", - }, - }, - ) - return spanstoremetrics.NewReaderDecorator(f.services.ArchiveStore.ArchiveSpanReader(), archiveMetricsFactory), nil -} - -// CreateArchiveSpanWriter implements storage.ArchiveFactory -func (f *Factory) CreateArchiveSpanWriter() (spanstore.Writer, error) { - if f.services.Capabilities == nil { - return nil, storage.ErrArchiveStorageNotSupported - } - capabilities, err := f.services.Capabilities.Capabilities() - if err != nil { - return nil, err - } - if capabilities == nil || !capabilities.ArchiveSpanWriter { - return nil, storage.ErrArchiveStorageNotSupported - } - return f.services.ArchiveStore.ArchiveSpanWriter(), nil -} - // Close closes the resources held by the factory func (f *Factory) Close() error { var errs []error @@ -241,3 +211,7 @@ func getTelset(logger *zap.Logger, tracerProvider trace.TracerProvider, meterPro MeterProvider: meterProvider, } } + +func (f *Factory) IsArchiveCapable() bool { + return f.options.namespace == archiveRemotePrefix && f.options.enabled +} diff --git a/plugin/storage/grpc/factory_test.go b/plugin/storage/grpc/factory_test.go index a9bf26c49c4..89d30731e00 100644 --- a/plugin/storage/grpc/factory_test.go +++ b/plugin/storage/grpc/factory_test.go @@ -28,7 +28,6 @@ import ( "github.com/jaegertracing/jaeger/pkg/tenancy" "github.com/jaegertracing/jaeger/plugin/storage/grpc/shared" "github.com/jaegertracing/jaeger/plugin/storage/grpc/shared/mocks" - "github.com/jaegertracing/jaeger/storage" "github.com/jaegertracing/jaeger/storage/dependencystore" dependencyStoreMocks "github.com/jaegertracing/jaeger/storage/dependencystore/mocks" "github.com/jaegertracing/jaeger/storage/spanstore" @@ -73,10 +72,6 @@ func makeMockServices() *ClientPluginServices { reader: new(spanStoreMocks.Reader), deps: new(dependencyStoreMocks.Reader), }, - ArchiveStore: &store{ - writer: new(spanStoreMocks.Writer), - reader: new(spanStoreMocks.Reader), - }, StreamingSpanWriter: &store{ writer: new(spanStoreMocks.Writer), }, @@ -88,7 +83,7 @@ func makeMockServices() *ClientPluginServices { func makeFactory(t *testing.T) *Factory { f := NewFactory() f.InitFromViper(viper.New(), zap.NewNop()) - f.config.ClientConfig.Endpoint = ":0" + f.options.ClientConfig.Endpoint = ":0" require.NoError(t, f.Initialize(metrics.NullFactory, zap.NewNop())) t.Cleanup(func() { @@ -115,7 +110,7 @@ func TestNewFactoryError(t *testing.T) { t.Run("viper", func(t *testing.T) { f := NewFactory() f.InitFromViper(viper.New(), zap.NewNop()) - f.config = *cfg + f.options.Config = *cfg err := f.Initialize(metrics.NullFactory, zap.NewNop()) assert.ErrorContains(t, err, "authenticator") }) @@ -189,20 +184,10 @@ func TestGRPCStorageFactory_Capabilities(t *testing.T) { capabilities := f.services.Capabilities.(*mocks.PluginCapabilities) capabilities.On("Capabilities"). Return(&shared.Capabilities{ - ArchiveSpanReader: true, - ArchiveSpanWriter: true, StreamingSpanWriter: true, - }, nil).Times(3) + }, nil).Times(1) - reader, err := f.CreateArchiveSpanReader() - require.NoError(t, err) - assert.NotNil(t, reader) - - writer, err := f.CreateArchiveSpanWriter() - require.NoError(t, err) - assert.NotNil(t, writer) - - writer, err = f.CreateSpanWriter() + writer, err := f.CreateSpanWriter() require.NoError(t, err) assert.NotNil(t, writer) } @@ -213,18 +198,10 @@ func TestGRPCStorageFactory_CapabilitiesDisabled(t *testing.T) { capabilities := f.services.Capabilities.(*mocks.PluginCapabilities) capabilities.On("Capabilities"). Return(&shared.Capabilities{ - ArchiveSpanReader: false, - ArchiveSpanWriter: false, StreamingSpanWriter: false, - }, nil).Times(3) - - reader, err := f.CreateArchiveSpanReader() - require.EqualError(t, err, storage.ErrArchiveStorageNotSupported.Error()) - assert.Nil(t, reader) - writer, err := f.CreateArchiveSpanWriter() - require.EqualError(t, err, storage.ErrArchiveStorageNotSupported.Error()) - assert.Nil(t, writer) - writer, err = f.CreateSpanWriter() + }, nil).Times(1) + + writer, err := f.CreateSpanWriter() require.NoError(t, err) assert.NotNil(t, writer, "regular span writer is available") } @@ -236,13 +213,7 @@ func TestGRPCStorageFactory_CapabilitiesError(t *testing.T) { customError := errors.New("made-up error") capabilities.On("Capabilities").Return(nil, customError) - reader, err := f.CreateArchiveSpanReader() - require.EqualError(t, err, customError.Error()) - assert.Nil(t, reader) - writer, err := f.CreateArchiveSpanWriter() - require.EqualError(t, err, customError.Error()) - assert.Nil(t, writer) - writer, err = f.CreateSpanWriter() + writer, err := f.CreateSpanWriter() require.NoError(t, err) assert.NotNil(t, writer, "regular span writer is available") } @@ -251,13 +222,7 @@ func TestGRPCStorageFactory_CapabilitiesNil(t *testing.T) { f := makeFactory(t) f.services.Capabilities = nil - reader, err := f.CreateArchiveSpanReader() - assert.Equal(t, err, storage.ErrArchiveStorageNotSupported) - assert.Nil(t, reader) - writer, err := f.CreateArchiveSpanWriter() - assert.Equal(t, err, storage.ErrArchiveStorageNotSupported) - assert.Nil(t, writer) - writer, err = f.CreateSpanWriter() + writer, err := f.CreateSpanWriter() require.NoError(t, err) assert.NotNil(t, writer, "regular span writer is available") } @@ -270,7 +235,7 @@ func TestWithCLIFlags(t *testing.T) { }) require.NoError(t, err) f.InitFromViper(v, zap.NewNop()) - assert.Equal(t, "foo:1234", f.config.ClientConfig.Endpoint) + assert.Equal(t, "foo:1234", f.options.Config.ClientConfig.Endpoint) require.NoError(t, f.Close()) } @@ -322,3 +287,46 @@ func TestStreamingSpanWriterFactory_Capabilities(t *testing.T) { err = writer.WriteSpan(context.Background(), nil) assert.ErrorContains(t, err, "I am streaming writer", "streaming writer when Capabilities return true") } + +func TestIsArchiveCapable(t *testing.T) { + tests := []struct { + name string + namespace string + enabled bool + expected bool + }{ + { + name: "archive capable", + namespace: "grpc-storage-archive", + enabled: true, + expected: true, + }, + { + name: "not capable", + namespace: "grpc-storage-archive", + enabled: false, + expected: false, + }, + { + name: "capable + wrong namespace", + namespace: "grpc-storage", + enabled: true, + expected: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + factory := &Factory{ + options: &options{ + namespace: test.namespace, + Config: Config{ + enabled: test.enabled, + }, + }, + } + result := factory.IsArchiveCapable() + require.Equal(t, test.expected, result) + }) + } +} diff --git a/plugin/storage/grpc/options.go b/plugin/storage/grpc/options.go index 3461cfc28a4..747e4a924ea 100644 --- a/plugin/storage/grpc/options.go +++ b/plugin/storage/grpc/options.go @@ -16,33 +16,57 @@ import ( const ( remotePrefix = "grpc-storage" - remoteServer = remotePrefix + ".server" - remoteConnectionTimeout = remotePrefix + ".connection-timeout" + archiveRemotePrefix = "grpc-storage-archive" + remoteServer = ".server" + remoteConnectionTimeout = ".connection-timeout" + enabled = ".enabled" defaultConnectionTimeout = time.Duration(5 * time.Second) ) -func tlsFlagsConfig() tlscfg.ClientFlagsConfig { +type options struct { + Config + namespace string +} + +func newOptions(namespace string) *options { + options := &options{ + Config: DefaultConfig(), + namespace: namespace, + } + return options +} + +func (opts *options) tlsFlagsConfig() tlscfg.ClientFlagsConfig { return tlscfg.ClientFlagsConfig{ - Prefix: remotePrefix, + Prefix: opts.namespace, } } // addFlags adds flags for Options -func addFlags(flagSet *flag.FlagSet) { - tlsFlagsConfig().AddFlags(flagSet) +func (opts *options) addFlags(flagSet *flag.FlagSet) { + opts.tlsFlagsConfig().AddFlags(flagSet) - flagSet.String(remoteServer, "", "The remote storage gRPC server address as host:port") - flagSet.Duration(remoteConnectionTimeout, defaultConnectionTimeout, "The remote storage gRPC server connection timeout") + flagSet.String(opts.namespace+remoteServer, "", "The remote storage gRPC server address as host:port") + flagSet.Duration(opts.namespace+remoteConnectionTimeout, defaultConnectionTimeout, "The remote storage gRPC server connection timeout") + if opts.namespace == archiveRemotePrefix { + flagSet.Bool( + opts.namespace+enabled, + false, + "Enable extra storage") + } } -func initFromViper(cfg *Config, v *viper.Viper) error { - cfg.ClientConfig.Endpoint = v.GetString(remoteServer) - remoteTLSCfg, err := tlsFlagsConfig().InitFromViper(v) +func (opts *options) initFromViper(cfg *Config, v *viper.Viper) error { + cfg.ClientConfig.Endpoint = v.GetString(opts.namespace + remoteServer) + remoteTLSCfg, err := opts.tlsFlagsConfig().InitFromViper(v) if err != nil { return fmt.Errorf("failed to parse gRPC storage TLS options: %w", err) } cfg.ClientConfig.TLSSetting = remoteTLSCfg - cfg.TimeoutConfig.Timeout = v.GetDuration(remoteConnectionTimeout) + cfg.TimeoutConfig.Timeout = v.GetDuration(opts.namespace + remoteConnectionTimeout) cfg.Tenancy = tenancy.InitFromViper(v) + if opts.namespace == archiveRemotePrefix { + cfg.enabled = v.GetBool(opts.namespace + enabled) + } return nil } diff --git a/plugin/storage/grpc/options_test.go b/plugin/storage/grpc/options_test.go index a3e8953f098..dc715e4119f 100644 --- a/plugin/storage/grpc/options_test.go +++ b/plugin/storage/grpc/options_test.go @@ -18,14 +18,15 @@ import ( ) func TestOptionsWithFlags(t *testing.T) { - v, command := config.Viperize(addFlags, tenancy.AddFlags) + opts := newOptions("grpc-storage") + v, command := config.Viperize(opts.addFlags, tenancy.AddFlags) err := command.ParseFlags([]string{ "--grpc-storage.server=foo:12345", "--multi-tenancy.header=x-scope-orgid", }) require.NoError(t, err) var cfg Config - require.NoError(t, initFromViper(&cfg, v)) + require.NoError(t, opts.initFromViper(&cfg, v)) assert.Equal(t, "foo:12345", cfg.ClientConfig.Endpoint) assert.False(t, cfg.Tenancy.Enabled) @@ -33,7 +34,8 @@ func TestOptionsWithFlags(t *testing.T) { } func TestRemoteOptionsWithFlags(t *testing.T) { - v, command := config.Viperize(addFlags) + opts := newOptions("grpc-storage") + v, command := config.Viperize(opts.addFlags) err := command.ParseFlags([]string{ "--grpc-storage.server=localhost:2001", "--grpc-storage.tls.enabled=true", @@ -41,7 +43,7 @@ func TestRemoteOptionsWithFlags(t *testing.T) { }) require.NoError(t, err) var cfg Config - require.NoError(t, initFromViper(&cfg, v)) + require.NoError(t, opts.initFromViper(&cfg, v)) assert.Equal(t, "localhost:2001", cfg.ClientConfig.Endpoint) assert.False(t, cfg.ClientConfig.TLSSetting.Insecure) @@ -49,7 +51,8 @@ func TestRemoteOptionsWithFlags(t *testing.T) { } func TestRemoteOptionsNoTLSWithFlags(t *testing.T) { - v, command := config.Viperize(addFlags) + opts := newOptions("grpc-storage") + v, command := config.Viperize(opts.addFlags) err := command.ParseFlags([]string{ "--grpc-storage.server=localhost:2001", "--grpc-storage.tls.enabled=false", @@ -57,7 +60,7 @@ func TestRemoteOptionsNoTLSWithFlags(t *testing.T) { }) require.NoError(t, err) var cfg Config - require.NoError(t, initFromViper(&cfg, v)) + require.NoError(t, opts.initFromViper(&cfg, v)) assert.Equal(t, "localhost:2001", cfg.ClientConfig.Endpoint) assert.True(t, cfg.ClientConfig.TLSSetting.Insecure) @@ -65,7 +68,8 @@ func TestRemoteOptionsNoTLSWithFlags(t *testing.T) { } func TestFailedTLSFlags(t *testing.T) { - v, command := config.Viperize(addFlags) + opts := newOptions("grpc-storage") + v, command := config.Viperize(opts.addFlags) err := command.ParseFlags([]string{ "--grpc-storage.tls.enabled=false", "--grpc-storage.tls.cert=blah", // invalid unless tls.enabled=true diff --git a/plugin/storage/grpc/shared/archive.go b/plugin/storage/grpc/shared/archive.go deleted file mode 100644 index 8776f7542b6..00000000000 --- a/plugin/storage/grpc/shared/archive.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) 2020 The Jaeger Authors. -// SPDX-License-Identifier: Apache-2.0 - -package shared - -import ( - "context" - "errors" - "fmt" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/jaegertracing/jaeger-idl/model/v1" - "github.com/jaegertracing/jaeger/proto-gen/storage_v1" - "github.com/jaegertracing/jaeger/storage/spanstore" -) - -var ( - _ spanstore.Reader = (*archiveReader)(nil) - _ spanstore.Writer = (*archiveWriter)(nil) -) - -// archiveReader wraps storage_v1.ArchiveSpanReaderPluginClient into spanstore.Reader -type archiveReader struct { - client storage_v1.ArchiveSpanReaderPluginClient -} - -// ArchiveWriter wraps storage_v1.ArchiveSpanWriterPluginClient into spanstore.Writer -type archiveWriter struct { - client storage_v1.ArchiveSpanWriterPluginClient -} - -// GetTrace takes a traceID and returns a Trace associated with that traceID from Archive Storage -func (r *archiveReader) GetTrace(ctx context.Context, q spanstore.GetTraceParameters) (*model.Trace, error) { - stream, err := r.client.GetArchiveTrace(ctx, &storage_v1.GetTraceRequest{ - TraceID: q.TraceID, - StartTime: q.StartTime, - EndTime: q.EndTime, - }) - if status.Code(err) == codes.NotFound { - return nil, spanstore.ErrTraceNotFound - } - if err != nil { - return nil, fmt.Errorf("plugin error: %w", err) - } - - return readTrace(stream) -} - -// GetServices not used in archiveReader -func (*archiveReader) GetServices(context.Context) ([]string, error) { - return nil, errors.New("GetServices not implemented") -} - -// GetOperations not used in archiveReader -func (*archiveReader) GetOperations(context.Context, spanstore.OperationQueryParameters) ([]spanstore.Operation, error) { - return nil, errors.New("GetOperations not implemented") -} - -// FindTraces not used in archiveReader -func (*archiveReader) FindTraces(context.Context, *spanstore.TraceQueryParameters) ([]*model.Trace, error) { - return nil, errors.New("FindTraces not implemented") -} - -// FindTraceIDs not used in archiveReader -func (*archiveReader) FindTraceIDs(context.Context, *spanstore.TraceQueryParameters) ([]model.TraceID, error) { - return nil, errors.New("FindTraceIDs not implemented") -} - -// WriteSpan saves the span into Archive Storage -func (w *archiveWriter) WriteSpan(ctx context.Context, span *model.Span) error { - _, err := w.client.WriteArchiveSpan(ctx, &storage_v1.WriteSpanRequest{ - Span: span, - }) - if err != nil { - return fmt.Errorf("plugin error: %w", err) - } - - return nil -} diff --git a/plugin/storage/grpc/shared/archive_test.go b/plugin/storage/grpc/shared/archive_test.go deleted file mode 100644 index 100d4b9ba26..00000000000 --- a/plugin/storage/grpc/shared/archive_test.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) 2020 The Jaeger Authors. -// SPDX-License-Identifier: Apache-2.0 - -package shared - -import ( - "context" - "io" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/jaegertracing/jaeger-idl/model/v1" - "github.com/jaegertracing/jaeger/proto-gen/storage_v1" - "github.com/jaegertracing/jaeger/proto-gen/storage_v1/mocks" - "github.com/jaegertracing/jaeger/storage/spanstore" -) - -func TestArchiveWriter_WriteSpan(t *testing.T) { - mockSpan := &model.Span{ - TraceID: mockTraceID, - SpanID: model.NewSpanID(1), - Process: &model.Process{}, - } - - archiveSpanWriter := new(mocks.ArchiveSpanWriterPluginClient) - archiveSpanWriter.On("WriteArchiveSpan", mock.Anything, &storage_v1.WriteSpanRequest{Span: mockSpan}). - Return(&storage_v1.WriteSpanResponse{}, nil) - writer := &archiveWriter{client: archiveSpanWriter} - - err := writer.WriteSpan(context.Background(), mockSpan) - require.NoError(t, err) -} - -func TestArchiveReader_GetTrace(t *testing.T) { - mockTraceID := model.NewTraceID(0, 123456) - mockSpan := model.Span{ - TraceID: mockTraceID, - SpanID: model.NewSpanID(1), - Process: &model.Process{}, - } - expected := &model.Trace{ - Spans: []*model.Span{&mockSpan}, - } - - traceClient := new(mocks.ArchiveSpanReaderPlugin_GetArchiveTraceClient) - traceClient.On("Recv").Return(&storage_v1.SpansResponseChunk{ - Spans: []model.Span{mockSpan}, - }, nil).Once() - traceClient.On("Recv").Return(nil, io.EOF) - - archiveSpanReader := new(mocks.ArchiveSpanReaderPluginClient) - archiveSpanReader.On("GetArchiveTrace", mock.Anything, &storage_v1.GetTraceRequest{ - TraceID: mockTraceID, - }).Return(traceClient, nil) - reader := &archiveReader{client: archiveSpanReader} - - trace, err := reader.GetTrace(context.Background(), spanstore.GetTraceParameters{ - TraceID: mockTraceID, - }) - require.NoError(t, err) - assert.Equal(t, expected, trace) -} - -func TestArchiveReaderGetTrace_NoTrace(t *testing.T) { - mockTraceID := model.NewTraceID(0, 123456) - - archiveSpanReader := new(mocks.ArchiveSpanReaderPluginClient) - archiveSpanReader.On("GetArchiveTrace", mock.Anything, &storage_v1.GetTraceRequest{ - TraceID: mockTraceID, - }).Return(nil, status.Errorf(codes.NotFound, "")) - reader := &archiveReader{client: archiveSpanReader} - - _, err := reader.GetTrace(context.Background(), spanstore.GetTraceParameters{ - TraceID: mockTraceID, - }) - assert.Equal(t, spanstore.ErrTraceNotFound, err) -} - -func TestArchiveReader_FindTraceIDs(t *testing.T) { - reader := archiveReader{client: &mocks.ArchiveSpanReaderPluginClient{}} - _, err := reader.FindTraceIDs(context.Background(), nil) - require.Error(t, err) -} - -func TestArchiveReader_FindTraces(t *testing.T) { - reader := archiveReader{client: &mocks.ArchiveSpanReaderPluginClient{}} - _, err := reader.FindTraces(context.Background(), nil) - require.Error(t, err) -} - -func TestArchiveReader_GetOperations(t *testing.T) { - reader := archiveReader{client: &mocks.ArchiveSpanReaderPluginClient{}} - _, err := reader.GetOperations(context.Background(), spanstore.OperationQueryParameters{}) - require.Error(t, err) -} - -func TestArchiveReader_GetServices(t *testing.T) { - reader := archiveReader{client: &mocks.ArchiveSpanReaderPluginClient{}} - _, err := reader.GetServices(context.Background()) - require.Error(t, err) -} diff --git a/plugin/storage/grpc/shared/grpc_client.go b/plugin/storage/grpc/shared/grpc_client.go index 7f464104b0e..b5a17383bd4 100644 --- a/plugin/storage/grpc/shared/grpc_client.go +++ b/plugin/storage/grpc/shared/grpc_client.go @@ -25,31 +25,26 @@ import ( const BearerTokenKey = "bearer.token" var ( - _ StoragePlugin = (*GRPCClient)(nil) - _ ArchiveStoragePlugin = (*GRPCClient)(nil) - _ PluginCapabilities = (*GRPCClient)(nil) + _ StoragePlugin = (*GRPCClient)(nil) + _ PluginCapabilities = (*GRPCClient)(nil) ) // GRPCClient implements shared.StoragePlugin and reads/writes spans and dependencies type GRPCClient struct { - readerClient storage_v1.SpanReaderPluginClient - writerClient storage_v1.SpanWriterPluginClient - archiveReaderClient storage_v1.ArchiveSpanReaderPluginClient - archiveWriterClient storage_v1.ArchiveSpanWriterPluginClient - capabilitiesClient storage_v1.PluginCapabilitiesClient - depsReaderClient storage_v1.DependenciesReaderPluginClient - streamWriterClient storage_v1.StreamingSpanWriterPluginClient + readerClient storage_v1.SpanReaderPluginClient + writerClient storage_v1.SpanWriterPluginClient + capabilitiesClient storage_v1.PluginCapabilitiesClient + depsReaderClient storage_v1.DependenciesReaderPluginClient + streamWriterClient storage_v1.StreamingSpanWriterPluginClient } func NewGRPCClient(tracedConn *grpc.ClientConn, untracedConn *grpc.ClientConn) *GRPCClient { return &GRPCClient{ - readerClient: storage_v1.NewSpanReaderPluginClient(tracedConn), - writerClient: storage_v1.NewSpanWriterPluginClient(untracedConn), - archiveReaderClient: storage_v1.NewArchiveSpanReaderPluginClient(tracedConn), - archiveWriterClient: storage_v1.NewArchiveSpanWriterPluginClient(untracedConn), - capabilitiesClient: storage_v1.NewPluginCapabilitiesClient(tracedConn), - depsReaderClient: storage_v1.NewDependenciesReaderPluginClient(tracedConn), - streamWriterClient: storage_v1.NewStreamingSpanWriterPluginClient(untracedConn), + readerClient: storage_v1.NewSpanReaderPluginClient(tracedConn), + writerClient: storage_v1.NewSpanWriterPluginClient(untracedConn), + capabilitiesClient: storage_v1.NewPluginCapabilitiesClient(tracedConn), + depsReaderClient: storage_v1.NewDependenciesReaderPluginClient(tracedConn), + streamWriterClient: storage_v1.NewStreamingSpanWriterPluginClient(untracedConn), } } @@ -72,14 +67,6 @@ func (c *GRPCClient) StreamingSpanWriter() spanstore.Writer { return newStreamingSpanWriter(c.streamWriterClient) } -func (c *GRPCClient) ArchiveSpanReader() spanstore.Reader { - return &archiveReader{client: c.archiveReaderClient} -} - -func (c *GRPCClient) ArchiveSpanWriter() spanstore.Writer { - return &archiveWriter{client: c.archiveWriterClient} -} - // GetTrace takes a traceID and returns a Trace associated with that traceID func (c *GRPCClient) GetTrace(ctx context.Context, query spanstore.GetTraceParameters) (*model.Trace, error) { stream, err := c.readerClient.GetTrace(ctx, &storage_v1.GetTraceRequest{ @@ -243,8 +230,6 @@ func (c *GRPCClient) Capabilities() (*Capabilities, error) { } return &Capabilities{ - ArchiveSpanReader: capabilities.ArchiveSpanReader, - ArchiveSpanWriter: capabilities.ArchiveSpanWriter, StreamingSpanWriter: capabilities.StreamingSpanWriter, }, nil } diff --git a/plugin/storage/grpc/shared/grpc_client_test.go b/plugin/storage/grpc/shared/grpc_client_test.go index bc96a77cec1..bfcd601896e 100644 --- a/plugin/storage/grpc/shared/grpc_client_test.go +++ b/plugin/storage/grpc/shared/grpc_client_test.go @@ -60,42 +60,34 @@ var ( ) type grpcClientTest struct { - client *GRPCClient - spanReader *grpcMocks.SpanReaderPluginClient - spanWriter *grpcMocks.SpanWriterPluginClient - archiveReader *grpcMocks.ArchiveSpanReaderPluginClient - archiveWriter *grpcMocks.ArchiveSpanWriterPluginClient - capabilities *grpcMocks.PluginCapabilitiesClient - depsReader *grpcMocks.DependenciesReaderPluginClient - streamWriter *grpcMocks.StreamingSpanWriterPluginClient + client *GRPCClient + spanReader *grpcMocks.SpanReaderPluginClient + spanWriter *grpcMocks.SpanWriterPluginClient + capabilities *grpcMocks.PluginCapabilitiesClient + depsReader *grpcMocks.DependenciesReaderPluginClient + streamWriter *grpcMocks.StreamingSpanWriterPluginClient } func withGRPCClient(fn func(r *grpcClientTest)) { spanReader := new(grpcMocks.SpanReaderPluginClient) - archiveReader := new(grpcMocks.ArchiveSpanReaderPluginClient) spanWriter := new(grpcMocks.SpanWriterPluginClient) - archiveWriter := new(grpcMocks.ArchiveSpanWriterPluginClient) depReader := new(grpcMocks.DependenciesReaderPluginClient) streamWriter := new(grpcMocks.StreamingSpanWriterPluginClient) capabilities := new(grpcMocks.PluginCapabilitiesClient) r := &grpcClientTest{ client: &GRPCClient{ - readerClient: spanReader, - writerClient: spanWriter, - archiveReaderClient: archiveReader, - archiveWriterClient: archiveWriter, - capabilitiesClient: capabilities, - depsReaderClient: depReader, - streamWriterClient: streamWriter, + readerClient: spanReader, + writerClient: spanWriter, + capabilitiesClient: capabilities, + depsReaderClient: depReader, + streamWriterClient: streamWriter, }, - spanReader: spanReader, - spanWriter: spanWriter, - archiveReader: archiveReader, - archiveWriter: archiveWriter, - depsReader: depReader, - capabilities: capabilities, - streamWriter: streamWriter, + spanReader: spanReader, + spanWriter: spanWriter, + depsReader: depReader, + capabilities: capabilities, + streamWriter: streamWriter, } fn(r) } @@ -107,8 +99,6 @@ func TestNewGRPCClient(t *testing.T) { assert.Implements(t, (*storage_v1.SpanReaderPluginClient)(nil), client.readerClient) assert.Implements(t, (*storage_v1.SpanWriterPluginClient)(nil), client.writerClient) - assert.Implements(t, (*storage_v1.ArchiveSpanReaderPluginClient)(nil), client.archiveReaderClient) - assert.Implements(t, (*storage_v1.ArchiveSpanWriterPluginClient)(nil), client.archiveWriterClient) assert.Implements(t, (*storage_v1.PluginCapabilitiesClient)(nil), client.capabilitiesClient) assert.Implements(t, (*storage_v1.DependenciesReaderPluginClient)(nil), client.depsReaderClient) assert.Implements(t, (*storage_v1.StreamingSpanWriterPluginClient)(nil), client.streamWriterClient) @@ -338,28 +328,6 @@ func TestGRPCClientGetDependencies(t *testing.T) { }) } -func TestGrpcClientWriteArchiveSpan(t *testing.T) { - withGRPCClient(func(r *grpcClientTest) { - r.archiveWriter.On("WriteArchiveSpan", mock.Anything, &storage_v1.WriteSpanRequest{ - Span: &mockTraceSpans[0], - }).Return(&storage_v1.WriteSpanResponse{}, nil) - - err := r.client.ArchiveSpanWriter().WriteSpan(context.Background(), &mockTraceSpans[0]) - require.NoError(t, err) - }) -} - -func TestGrpcClientWriteArchiveSpan_Error(t *testing.T) { - withGRPCClient(func(r *grpcClientTest) { - r.archiveWriter.On("WriteArchiveSpan", mock.Anything, &storage_v1.WriteSpanRequest{ - Span: &mockTraceSpans[0], - }).Return(nil, status.Error(codes.Internal, "internal error")) - - err := r.client.ArchiveSpanWriter().WriteSpan(context.Background(), &mockTraceSpans[0]) - require.Error(t, err) - }) -} - func TestGrpcClientStreamWriterWriteSpan(t *testing.T) { withGRPCClient(func(r *grpcClientTest) { stream := new(grpcMocks.StreamingSpanWriterPlugin_WriteSpanStreamClient) @@ -370,87 +338,6 @@ func TestGrpcClientStreamWriterWriteSpan(t *testing.T) { }) } -func TestGrpcClientGetArchiveTrace(t *testing.T) { - withGRPCClient(func(r *grpcClientTest) { - startTime := time.Date(2020, time.January, 1, 13, 0, 0, 0, time.UTC) - endTime := time.Date(2020, time.January, 1, 14, 0, 0, 0, time.UTC) - traceClient := new(grpcMocks.ArchiveSpanReaderPlugin_GetArchiveTraceClient) - traceClient.On("Recv").Return(&storage_v1.SpansResponseChunk{ - Spans: mockTraceSpans, - }, nil).Once() - traceClient.On("Recv").Return(nil, io.EOF) - r.archiveReader.On("GetArchiveTrace", mock.Anything, &storage_v1.GetTraceRequest{ - TraceID: mockTraceID, - StartTime: startTime, - EndTime: endTime, - }).Return(traceClient, nil) - - var expectedSpans []*model.Span - for i := range mockTraceSpans { - expectedSpans = append(expectedSpans, &mockTraceSpans[i]) - } - - s, err := r.client.ArchiveSpanReader().GetTrace(context.Background(), spanstore.GetTraceParameters{ - TraceID: mockTraceID, - StartTime: startTime, - EndTime: endTime, - }) - require.NoError(t, err) - assert.Equal(t, &model.Trace{ - Spans: expectedSpans, - }, s) - }) -} - -func TestGrpcClientGetArchiveTrace_StreamError(t *testing.T) { - withGRPCClient(func(r *grpcClientTest) { - traceClient := new(grpcMocks.ArchiveSpanReaderPlugin_GetArchiveTraceClient) - traceClient.On("Recv").Return(nil, errors.New("an error")) - r.archiveReader.On("GetArchiveTrace", mock.Anything, &storage_v1.GetTraceRequest{ - TraceID: mockTraceID, - }).Return(traceClient, nil) - - s, err := r.client.ArchiveSpanReader().GetTrace(context.Background(), spanstore.GetTraceParameters{ - TraceID: mockTraceID, - }) - require.Error(t, err) - assert.Nil(t, s) - }) -} - -func TestGrpcClientGetArchiveTrace_NoTrace(t *testing.T) { - withGRPCClient(func(r *grpcClientTest) { - r.archiveReader.On("GetArchiveTrace", mock.Anything, &storage_v1.GetTraceRequest{ - TraceID: mockTraceID, - }).Return(nil, spanstore.ErrTraceNotFound) - - s, err := r.client.ArchiveSpanReader().GetTrace( - context.Background(), - spanstore.GetTraceParameters{ - TraceID: mockTraceID, - }, - ) - require.Error(t, err) - assert.Nil(t, s) - }) -} - -func TestGrpcClientGetArchiveTrace_StreamErrorTraceNotFound(t *testing.T) { - withGRPCClient(func(r *grpcClientTest) { - traceClient := new(grpcMocks.ArchiveSpanReaderPlugin_GetArchiveTraceClient) - traceClient.On("Recv").Return(nil, spanstore.ErrTraceNotFound) - r.archiveReader.On("GetArchiveTrace", mock.Anything, &storage_v1.GetTraceRequest{ - TraceID: mockTraceID, - }).Return(traceClient, nil) - - s, err := r.client.ArchiveSpanReader().GetTrace(context.Background(), spanstore.GetTraceParameters{ - TraceID: mockTraceID, - }) - assert.Equal(t, spanstore.ErrTraceNotFound, err) - assert.Nil(t, s) - }) -} - func TestGrpcClientCapabilities(t *testing.T) { withGRPCClient(func(r *grpcClientTest) { r.capabilities.On("Capabilities", mock.Anything, &storage_v1.CapabilitiesRequest{}). @@ -459,8 +346,6 @@ func TestGrpcClientCapabilities(t *testing.T) { capabilities, err := r.client.Capabilities() require.NoError(t, err) assert.Equal(t, &Capabilities{ - ArchiveSpanReader: true, - ArchiveSpanWriter: true, StreamingSpanWriter: true, }, capabilities) }) @@ -474,8 +359,6 @@ func TestGrpcClientCapabilities_NotSupported(t *testing.T) { capabilities, err := r.client.Capabilities() require.NoError(t, err) assert.Equal(t, &Capabilities{ - ArchiveSpanReader: false, - ArchiveSpanWriter: false, StreamingSpanWriter: false, }, capabilities) }) diff --git a/plugin/storage/grpc/shared/grpc_handler_test.go b/plugin/storage/grpc/shared/grpc_handler_test.go index d8942a86c81..1c3cc7cc838 100644 --- a/plugin/storage/grpc/shared/grpc_handler_test.go +++ b/plugin/storage/grpc/shared/grpc_handler_test.go @@ -26,20 +26,10 @@ import ( ) type mockStoragePlugin struct { - spanReader *spanStoreMocks.Reader - spanWriter *spanStoreMocks.Writer - archiveReader *spanStoreMocks.Reader - archiveWriter *spanStoreMocks.Writer - depsReader *dependencyStoreMocks.Reader - streamWriter *spanStoreMocks.Writer -} - -func (plugin *mockStoragePlugin) ArchiveSpanReader() spanstore.Reader { - return plugin.archiveReader -} - -func (plugin *mockStoragePlugin) ArchiveSpanWriter() spanstore.Writer { - return plugin.archiveWriter + spanReader *spanStoreMocks.Reader + spanWriter *spanStoreMocks.Writer + depsReader *dependencyStoreMocks.Reader + streamWriter *spanStoreMocks.Writer } func (plugin *mockStoragePlugin) SpanReader() spanstore.Reader { @@ -66,18 +56,14 @@ type grpcServerTest struct { func withGRPCServer(fn func(r *grpcServerTest)) { spanReader := new(spanStoreMocks.Reader) spanWriter := new(spanStoreMocks.Writer) - archiveReader := new(spanStoreMocks.Reader) - archiveWriter := new(spanStoreMocks.Writer) depReader := new(dependencyStoreMocks.Reader) streamWriter := new(spanStoreMocks.Writer) mockPlugin := &mockStoragePlugin{ - spanReader: spanReader, - spanWriter: spanWriter, - archiveReader: archiveReader, - archiveWriter: archiveWriter, - depsReader: depReader, - streamWriter: streamWriter, + spanReader: spanReader, + spanWriter: spanWriter, + depsReader: depReader, + streamWriter: streamWriter, } impl := &GRPCHandlerStorageImpl{ @@ -91,10 +77,10 @@ func withGRPCServer(fn func(r *grpcServerTest)) { return mockPlugin.depsReader }, ArchiveSpanReader: func() spanstore.Reader { - return mockPlugin.archiveReader + return mockPlugin.spanReader }, ArchiveSpanWriter: func() spanstore.Writer { - return mockPlugin.archiveWriter + return mockPlugin.spanWriter }, StreamingSpanWriter: func() spanstore.Writer { return mockPlugin.streamWriter @@ -305,7 +291,7 @@ func TestGRPCServerGetArchiveTrace(t *testing.T) { for i := range mockTraceSpans { traceSpans = append(traceSpans, &mockTraceSpans[i]) } - r.impl.archiveReader.On("GetTrace", mock.Anything, spanstore.GetTraceParameters{TraceID: mockTraceID}). + r.impl.spanReader.On("GetTrace", mock.Anything, spanstore.GetTraceParameters{TraceID: mockTraceID}). Return(&model.Trace{Spans: traceSpans}, nil) err := r.server.GetArchiveTrace(&storage_v1.GetTraceRequest{ @@ -320,7 +306,7 @@ func TestGRPCServerGetArchiveTrace_NotFound(t *testing.T) { traceSteam := new(grpcMocks.SpanReaderPlugin_GetTraceServer) traceSteam.On("Context").Return(context.Background()) - r.impl.archiveReader.On("GetTrace", mock.Anything, spanstore.GetTraceParameters{TraceID: mockTraceID}). + r.impl.spanReader.On("GetTrace", mock.Anything, spanstore.GetTraceParameters{TraceID: mockTraceID}). Return(nil, spanstore.ErrTraceNotFound) err := r.server.GetArchiveTrace(&storage_v1.GetTraceRequest{ @@ -335,7 +321,7 @@ func TestGRPCServerGetArchiveTrace_Error(t *testing.T) { traceSteam := new(grpcMocks.SpanReaderPlugin_GetTraceServer) traceSteam.On("Context").Return(context.Background()) - r.impl.archiveReader.On("GetTrace", mock.Anything, spanstore.GetTraceParameters{TraceID: mockTraceID}). + r.impl.spanReader.On("GetTrace", mock.Anything, spanstore.GetTraceParameters{TraceID: mockTraceID}). Return(nil, errors.New("some error")) err := r.server.GetArchiveTrace(&storage_v1.GetTraceRequest{ @@ -350,7 +336,7 @@ func TestGRPCServerGetArchiveTrace_NoImpl(t *testing.T) { r.server.impl.ArchiveSpanReader = func() spanstore.Reader { return nil } traceSteam := new(grpcMocks.SpanReaderPlugin_GetTraceServer) - r.impl.archiveReader.On("GetTrace", mock.Anything, spanstore.GetTraceParameters{TraceID: mockTraceID}). + r.impl.spanReader.On("GetTrace", mock.Anything, spanstore.GetTraceParameters{TraceID: mockTraceID}). Return(nil, errors.New("some error")) err := r.server.GetArchiveTrace(&storage_v1.GetTraceRequest{ @@ -371,7 +357,7 @@ func TestGRPCServerGetArchiveTrace_StreamError(t *testing.T) { for i := range mockTraceSpans { traceSpans = append(traceSpans, &mockTraceSpans[i]) } - r.impl.archiveReader.On("GetTrace", mock.Anything, spanstore.GetTraceParameters{TraceID: mockTraceID}). + r.impl.spanReader.On("GetTrace", mock.Anything, spanstore.GetTraceParameters{TraceID: mockTraceID}). Return(&model.Trace{Spans: traceSpans}, nil) err := r.server.GetArchiveTrace(&storage_v1.GetTraceRequest{ @@ -394,7 +380,7 @@ func TestGRPCServerWriteArchiveSpan_NoImpl(t *testing.T) { func TestGRPCServerWriteArchiveSpan(t *testing.T) { withGRPCServer(func(r *grpcServerTest) { - r.impl.archiveWriter.On("WriteSpan", mock.Anything, &mockTraceSpans[0]). + r.impl.spanWriter.On("WriteSpan", mock.Anything, &mockTraceSpans[0]). Return(nil) s, err := r.server.WriteArchiveSpan(context.Background(), &storage_v1.WriteSpanRequest{ @@ -407,7 +393,7 @@ func TestGRPCServerWriteArchiveSpan(t *testing.T) { func TestGRPCServerWriteArchiveSpan_Error(t *testing.T) { withGRPCServer(func(r *grpcServerTest) { - r.impl.archiveWriter.On("WriteSpan", mock.Anything, &mockTraceSpans[0]). + r.impl.spanWriter.On("WriteSpan", mock.Anything, &mockTraceSpans[0]). Return(errors.New("some error")) _, err := r.server.WriteArchiveSpan(context.Background(), &storage_v1.WriteSpanRequest{ diff --git a/plugin/storage/grpc/shared/interface.go b/plugin/storage/grpc/shared/interface.go index 9abc2f75dce..42a7f91937f 100644 --- a/plugin/storage/grpc/shared/interface.go +++ b/plugin/storage/grpc/shared/interface.go @@ -15,12 +15,6 @@ type StoragePlugin interface { DependencyReader() dependencystore.Reader } -// ArchiveStoragePlugin is the interface we're exposing as a plugin. -type ArchiveStoragePlugin interface { - ArchiveSpanReader() spanstore.Reader - ArchiveSpanWriter() spanstore.Writer -} - // StreamingSpanWriterPlugin is the interface we're exposing as a plugin. type StreamingSpanWriterPlugin interface { StreamingSpanWriter() spanstore.Writer @@ -33,14 +27,11 @@ type PluginCapabilities interface { // Capabilities contains information about plugin capabilities type Capabilities struct { - ArchiveSpanReader bool - ArchiveSpanWriter bool StreamingSpanWriter bool } // PluginServices defines services plugin can expose type PluginServices struct { Store StoragePlugin - ArchiveStore ArchiveStoragePlugin StreamingSpanWriter StreamingSpanWriterPlugin } diff --git a/plugin/storage/integration/cassandra_test.go b/plugin/storage/integration/cassandra_test.go index 161a695cc03..a3aeddcc9a8 100644 --- a/plugin/storage/integration/cassandra_test.go +++ b/plugin/storage/integration/cassandra_test.go @@ -24,7 +24,8 @@ import ( type CassandraStorageIntegration struct { StorageIntegration - factory *cassandra.Factory + factory *cassandra.Factory + archiveFactory *cassandra.Factory } func newCassandraStorageIntegration() *CassandraStorageIntegration { @@ -41,11 +42,12 @@ func newCassandraStorageIntegration() *CassandraStorageIntegration { func (s *CassandraStorageIntegration) cleanUp(t *testing.T) { require.NoError(t, s.factory.Purge(context.Background())) + require.NoError(t, s.archiveFactory.Purge(context.Background())) } -func (*CassandraStorageIntegration) initializeCassandraFactory(t *testing.T, flags []string) *cassandra.Factory { +func (*CassandraStorageIntegration) initializeCassandraFactory(t *testing.T, flags []string, factoryInit func() *cassandra.Factory) *cassandra.Factory { logger := zaptest.NewLogger(t, zaptest.WrapOptions(zap.AddCaller())) - f := cassandra.NewFactory() + f := factoryInit() v, command := config.Viperize(f.AddFlags) require.NoError(t, command.ParseFlags(flags)) f.InitFromViper(v, logger) @@ -64,14 +66,17 @@ func (s *CassandraStorageIntegration) initializeCassandra(t *testing.T) { "--cassandra.password=" + password, "--cassandra.username=" + username, "--cassandra.keyspace=jaeger_v1_dc1", + }, cassandra.NewFactory) + af := s.initializeCassandraFactory(t, []string{ "--cassandra-archive.keyspace=jaeger_v1_dc1_archive", "--cassandra-archive.enabled=true", "--cassandra-archive.servers=127.0.0.1", "--cassandra-archive.basic.allowed-authenticators=org.apache.cassandra.auth.PasswordAuthenticator", "--cassandra-archive.password=" + password, "--cassandra-archive.username=" + username, - }) + }, cassandra.NewArchiveFactory) s.factory = f + s.archiveFactory = af var err error spanWriter, err := f.CreateSpanWriter() require.NoError(t, err) @@ -79,9 +84,9 @@ func (s *CassandraStorageIntegration) initializeCassandra(t *testing.T) { spanReader, err := f.CreateSpanReader() require.NoError(t, err) s.TraceReader = v1adapter.NewTraceReader(spanReader) - s.ArchiveSpanReader, err = f.CreateArchiveSpanReader() + s.ArchiveSpanReader, err = af.CreateSpanReader() require.NoError(t, err) - s.ArchiveSpanWriter, err = f.CreateArchiveSpanWriter() + s.ArchiveSpanWriter, err = af.CreateSpanWriter() require.NoError(t, err) s.SamplingStore, err = f.CreateSamplingStore(0) require.NoError(t, err) diff --git a/plugin/storage/integration/elasticsearch_test.go b/plugin/storage/integration/elasticsearch_test.go index 016c7fb87ef..b0d2c84bc25 100644 --- a/plugin/storage/integration/elasticsearch_test.go +++ b/plugin/storage/integration/elasticsearch_test.go @@ -52,7 +52,8 @@ type ESStorageIntegration struct { client *elastic.Client v8Client *elasticsearch8.Client - factory *es.Factory + factory *es.Factory + archiveFactory *es.Factory } func (s *ESStorageIntegration) getVersion() (uint, error) { @@ -100,25 +101,13 @@ func (s *ESStorageIntegration) initializeES(t *testing.T, c *http.Client, allTag func (s *ESStorageIntegration) esCleanUp(t *testing.T) { require.NoError(t, s.factory.Purge(context.Background())) + require.NoError(t, s.archiveFactory.Purge(context.Background())) } -func (*ESStorageIntegration) initializeESFactory(t *testing.T, allTagsAsFields bool) *es.Factory { +func (*ESStorageIntegration) initializeESFactory(t *testing.T, args []string, factoryInit func() *es.Factory) *es.Factory { logger := zaptest.NewLogger(t, zaptest.WrapOptions(zap.AddCaller())) - f := es.NewFactory() + f := factoryInit() v, command := config.Viperize(f.AddFlags) - args := []string{ - fmt.Sprintf("--es.num-shards=%v", 5), - fmt.Sprintf("--es.num-replicas=%v", 1), - fmt.Sprintf("--es.index-prefix=%v", indexPrefix), - fmt.Sprintf("--es.use-ilm=%v", false), - fmt.Sprintf("--es.service-cache-ttl=%v", 1*time.Second), - fmt.Sprintf("--es.tags-as-fields.all=%v", allTagsAsFields), - fmt.Sprintf("--es.bulk.actions=%v", 1), - fmt.Sprintf("--es.bulk.flush-interval=%v", time.Nanosecond), - "--es-archive.enabled=true", - fmt.Sprintf("--es-archive.tags-as-fields.all=%v", allTagsAsFields), - fmt.Sprintf("--es-archive.index-prefix=%v", indexPrefix), - } require.NoError(t, command.ParseFlags(args)) f.InitFromViper(v, logger) require.NoError(t, f.Initialize(metrics.NullFactory, logger)) @@ -130,8 +119,23 @@ func (*ESStorageIntegration) initializeESFactory(t *testing.T, allTagsAsFields b } func (s *ESStorageIntegration) initSpanstore(t *testing.T, allTagsAsFields bool) { - f := s.initializeESFactory(t, allTagsAsFields) + f := s.initializeESFactory(t, []string{ + fmt.Sprintf("--es.num-shards=%v", 5), + fmt.Sprintf("--es.num-replicas=%v", 1), + fmt.Sprintf("--es.index-prefix=%v", indexPrefix), + fmt.Sprintf("--es.use-ilm=%v", false), + fmt.Sprintf("--es.service-cache-ttl=%v", 1*time.Second), + fmt.Sprintf("--es.tags-as-fields.all=%v", allTagsAsFields), + fmt.Sprintf("--es.bulk.actions=%v", 1), + fmt.Sprintf("--es.bulk.flush-interval=%v", time.Nanosecond), + }, es.NewFactory) + af := s.initializeESFactory(t, []string{ + "--es-archive.enabled=true", + fmt.Sprintf("--es-archive.tags-as-fields.all=%v", allTagsAsFields), + fmt.Sprintf("--es-archive.index-prefix=%v", indexPrefix), + }, es.NewArchiveFactory) s.factory = f + s.archiveFactory = af var err error spanWriter, err := f.CreateSpanWriter() require.NoError(t, err) @@ -139,9 +143,9 @@ func (s *ESStorageIntegration) initSpanstore(t *testing.T, allTagsAsFields bool) spanReader, err := f.CreateSpanReader() require.NoError(t, err) s.TraceReader = v1adapter.NewTraceReader(spanReader) - s.ArchiveSpanReader, err = f.CreateArchiveSpanReader() + s.ArchiveSpanReader, err = af.CreateSpanReader() require.NoError(t, err) - s.ArchiveSpanWriter, err = f.CreateArchiveSpanWriter() + s.ArchiveSpanWriter, err = af.CreateSpanWriter() require.NoError(t, err) dependencyReader, err := f.CreateDependencyReader() diff --git a/plugin/storage/integration/grpc_test.go b/plugin/storage/integration/grpc_test.go index 9d8e85bc29e..76e089b1e16 100644 --- a/plugin/storage/integration/grpc_test.go +++ b/plugin/storage/integration/grpc_test.go @@ -15,27 +15,37 @@ import ( "github.com/jaegertracing/jaeger/pkg/metrics" "github.com/jaegertracing/jaeger/pkg/testutils" "github.com/jaegertracing/jaeger/plugin/storage/grpc" + "github.com/jaegertracing/jaeger/ports" "github.com/jaegertracing/jaeger/storage_v2/v1adapter" ) type GRPCStorageIntegrationTestSuite struct { StorageIntegration - flags []string - factory *grpc.Factory - remoteStorage *RemoteMemoryStorage + flags []string + archiveFlags []string + factory *grpc.Factory + archiveFactory *grpc.Factory + remoteStorage *RemoteMemoryStorage + archiveRemoteStorage *RemoteMemoryStorage } func (s *GRPCStorageIntegrationTestSuite) initialize(t *testing.T) { logger := zaptest.NewLogger(t, zaptest.WrapOptions(zap.AddCaller())) - s.remoteStorage = StartNewRemoteMemoryStorage(t) + s.remoteStorage = StartNewRemoteMemoryStorage(t, ports.RemoteStorageGRPC) + s.archiveRemoteStorage = StartNewRemoteMemoryStorage(t, ports.RemoteStorageGRPC+1) + initFactory := func(f *grpc.Factory, flags []string) { + v, command := config.Viperize(f.AddFlags) + require.NoError(t, command.ParseFlags(flags)) + f.InitFromViper(v, logger) + require.NoError(t, f.Initialize(metrics.NullFactory, logger)) + } f := grpc.NewFactory() - v, command := config.Viperize(f.AddFlags) - err := command.ParseFlags(s.flags) - require.NoError(t, err) - f.InitFromViper(v, logger) - require.NoError(t, f.Initialize(metrics.NullFactory, logger)) + af := grpc.NewArchiveFactory() + initFactory(f, s.flags) + initFactory(af, s.archiveFlags) s.factory = f + s.archiveFactory = af spanWriter, err := f.CreateSpanWriter() require.NoError(t, err) @@ -43,9 +53,9 @@ func (s *GRPCStorageIntegrationTestSuite) initialize(t *testing.T) { spanReader, err := f.CreateSpanReader() require.NoError(t, err) s.TraceReader = v1adapter.NewTraceReader(spanReader) - s.ArchiveSpanReader, err = f.CreateArchiveSpanReader() + s.ArchiveSpanReader, err = af.CreateSpanReader() require.NoError(t, err) - s.ArchiveSpanWriter, err = f.CreateArchiveSpanWriter() + s.ArchiveSpanWriter, err = af.CreateSpanWriter() require.NoError(t, err) // TODO DependencyWriter is not implemented in grpc store @@ -55,7 +65,9 @@ func (s *GRPCStorageIntegrationTestSuite) initialize(t *testing.T) { func (s *GRPCStorageIntegrationTestSuite) close(t *testing.T) { require.NoError(t, s.factory.Close()) + require.NoError(t, s.archiveFactory.Close()) s.remoteStorage.Close(t) + s.archiveRemoteStorage.Close(t) } func (s *GRPCStorageIntegrationTestSuite) cleanUp(t *testing.T) { @@ -73,6 +85,11 @@ func TestGRPCRemoteStorage(t *testing.T) { "--grpc-storage.server=localhost:17271", "--grpc-storage.tls.enabled=false", }, + archiveFlags: []string{ + "--grpc-storage-archive.enabled=true", + "--grpc-storage-archive.server=localhost:17272", + "--grpc-storage-archive.tls.enabled=false", + }, } s.initialize(t) defer s.close(t) diff --git a/plugin/storage/integration/remote_memory_storage.go b/plugin/storage/integration/remote_memory_storage.go index 8e72fadf47c..d569abbc65f 100644 --- a/plugin/storage/integration/remote_memory_storage.go +++ b/plugin/storage/integration/remote_memory_storage.go @@ -33,12 +33,12 @@ type RemoteMemoryStorage struct { storageFactory *storage.Factory } -func StartNewRemoteMemoryStorage(t *testing.T) *RemoteMemoryStorage { +func StartNewRemoteMemoryStorage(t *testing.T, port int) *RemoteMemoryStorage { logger := zaptest.NewLogger(t, zaptest.WrapOptions(zap.AddCaller())) opts := &app.Options{ ServerConfig: configgrpc.ServerConfig{ NetAddr: confignet.AddrConfig{ - Endpoint: ports.PortToHostPort(ports.RemoteStorageGRPC), + Endpoint: ports.PortToHostPort(port), }, }, Tenancy: tenancy.Options{ diff --git a/plugin/storage/memory/factory.go b/plugin/storage/memory/factory.go index 0a7b7b34a24..a784c5e22dc 100644 --- a/plugin/storage/memory/factory.go +++ b/plugin/storage/memory/factory.go @@ -24,7 +24,6 @@ import ( var ( // interface comformance checks _ storage.Factory = (*Factory)(nil) - _ storage.ArchiveFactory = (*Factory)(nil) _ storage.SamplingStoreFactory = (*Factory)(nil) _ plugin.Configurable = (*Factory)(nil) _ storage.Purger = (*Factory)(nil) @@ -90,23 +89,6 @@ func (f *Factory) CreateSpanWriter() (spanstore.Writer, error) { return f.store, nil } -// CreateArchiveSpanReader implements storage.ArchiveFactory -func (f *Factory) CreateArchiveSpanReader() (spanstore.Reader, error) { - archiveMetricsFactory := f.metricsFactory.Namespace( - metrics.NSOptions{ - Tags: map[string]string{ - "role": "archive", - }, - }, - ) - return spanstoremetrics.NewReaderDecorator(f.store, archiveMetricsFactory), nil -} - -// CreateArchiveSpanWriter implements storage.ArchiveFactory -func (f *Factory) CreateArchiveSpanWriter() (spanstore.Writer, error) { - return f.store, nil -} - // CreateDependencyReader implements storage.Factory func (f *Factory) CreateDependencyReader() (dependencystore.Reader, error) { return f.store, nil diff --git a/storage/factory.go b/storage/factory.go index fdac5e9d17b..da07bcd4ca8 100644 --- a/storage/factory.go +++ b/storage/factory.go @@ -6,7 +6,6 @@ package storage import ( "context" - "errors" "go.uber.org/zap" @@ -61,23 +60,6 @@ type SamplingStoreFactory interface { CreateSamplingStore(maxBuckets int) (samplingstore.Store, error) } -var ( - // ErrArchiveStorageNotConfigured can be returned by the ArchiveFactory when the archive storage is not configured. - ErrArchiveStorageNotConfigured = errors.New("archive storage not configured") - - // ErrArchiveStorageNotSupported can be returned by the ArchiveFactory when the archive storage is not supported by the backend. - ErrArchiveStorageNotSupported = errors.New("archive storage not supported") -) - -// ArchiveFactory is an additional interface that can be implemented by a factory to support trace archiving. -type ArchiveFactory interface { - // CreateArchiveSpanReader creates a spanstore.Reader. - CreateArchiveSpanReader() (spanstore.Reader, error) - - // CreateArchiveSpanWriter creates a spanstore.Writer. - CreateArchiveSpanWriter() (spanstore.Writer, error) -} - // MetricStoreFactory defines an interface for a factory that can create implementations of different metrics storage components. // Implementations are also encouraged to implement plugin.Configurable interface. // @@ -92,3 +74,15 @@ type MetricStoreFactory interface { // CreateMetricsReader creates a metricstore.Reader. CreateMetricsReader() (metricstore.Reader, error) } + +// Inheritable is an interface that can be implement by some storage implementations +// to provide a way to inherit configuration settings from another factory. +type Inheritable interface { + InheritSettingsFrom(other Factory) +} + +// ArchiveCapable is an interface that can be implemented by some storage implementations +// to indicate that they are capable of archiving data. +type ArchiveCapable interface { + IsArchiveCapable() bool +} diff --git a/storage/mocks/ArchiveCapable.go b/storage/mocks/ArchiveCapable.go new file mode 100644 index 00000000000..aef23e66bf4 --- /dev/null +++ b/storage/mocks/ArchiveCapable.go @@ -0,0 +1,47 @@ +// Copyright (c) The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 +// +// Run 'make generate-mocks' to regenerate. + +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" + +// ArchiveCapable is an autogenerated mock type for the ArchiveCapable type +type ArchiveCapable struct { + mock.Mock +} + +// IsArchiveCapable provides a mock function with no fields +func (_m *ArchiveCapable) IsArchiveCapable() bool { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for IsArchiveCapable") + } + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// NewArchiveCapable creates a new instance of ArchiveCapable. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewArchiveCapable(t interface { + mock.TestingT + Cleanup(func()) +}) *ArchiveCapable { + mock := &ArchiveCapable{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/storage/mocks/ArchiveFactory.go b/storage/mocks/ArchiveFactory.go deleted file mode 100644 index 294b46dec58..00000000000 --- a/storage/mocks/ArchiveFactory.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) The Jaeger Authors. -// SPDX-License-Identifier: Apache-2.0 -// -// Run 'make generate-mocks' to regenerate. - -// Code generated by mockery. DO NOT EDIT. - -package mocks - -import ( - spanstore "github.com/jaegertracing/jaeger/storage/spanstore" - mock "github.com/stretchr/testify/mock" -) - -// ArchiveFactory is an autogenerated mock type for the ArchiveFactory type -type ArchiveFactory struct { - mock.Mock -} - -// CreateArchiveSpanReader provides a mock function with no fields -func (_m *ArchiveFactory) CreateArchiveSpanReader() (spanstore.Reader, error) { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for CreateArchiveSpanReader") - } - - var r0 spanstore.Reader - var r1 error - if rf, ok := ret.Get(0).(func() (spanstore.Reader, error)); ok { - return rf() - } - if rf, ok := ret.Get(0).(func() spanstore.Reader); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(spanstore.Reader) - } - } - - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// CreateArchiveSpanWriter provides a mock function with no fields -func (_m *ArchiveFactory) CreateArchiveSpanWriter() (spanstore.Writer, error) { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for CreateArchiveSpanWriter") - } - - var r0 spanstore.Writer - var r1 error - if rf, ok := ret.Get(0).(func() (spanstore.Writer, error)); ok { - return rf() - } - if rf, ok := ret.Get(0).(func() spanstore.Writer); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(spanstore.Writer) - } - } - - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// NewArchiveFactory creates a new instance of ArchiveFactory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewArchiveFactory(t interface { - mock.TestingT - Cleanup(func()) -}) *ArchiveFactory { - mock := &ArchiveFactory{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/storage/mocks/Inheritable.go b/storage/mocks/Inheritable.go new file mode 100644 index 00000000000..150bf34ab1e --- /dev/null +++ b/storage/mocks/Inheritable.go @@ -0,0 +1,37 @@ +// Copyright (c) The Jaeger Authors. +// SPDX-License-Identifier: Apache-2.0 +// +// Run 'make generate-mocks' to regenerate. + +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + storage "github.com/jaegertracing/jaeger/storage" + mock "github.com/stretchr/testify/mock" +) + +// Inheritable is an autogenerated mock type for the Inheritable type +type Inheritable struct { + mock.Mock +} + +// InheritSettingsFrom provides a mock function with given fields: other +func (_m *Inheritable) InheritSettingsFrom(other storage.Factory) { + _m.Called(other) +} + +// NewInheritable creates a new instance of Inheritable. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewInheritable(t interface { + mock.TestingT + Cleanup(func()) +}) *Inheritable { + mock := &Inheritable{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/storage_v2/v1adapter/archive.go b/storage_v2/v1adapter/archive.go deleted file mode 100644 index bdf31fbf6bc..00000000000 --- a/storage_v2/v1adapter/archive.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2025 The Jaeger Authors. -// SPDX-License-Identifier: Apache-2.0 - -package v1adapter - -import ( - "errors" - - "go.uber.org/zap" - - "github.com/jaegertracing/jaeger/storage" -) - -func InitializeArchiveStorage( - storageFactory storage.BaseFactory, - logger *zap.Logger, -) (*TraceReader, *TraceWriter) { - archiveFactory, ok := storageFactory.(storage.ArchiveFactory) - if !ok { - logger.Info("Archive storage not supported by the factory") - return nil, nil - } - reader, err := archiveFactory.CreateArchiveSpanReader() - if errors.Is(err, storage.ErrArchiveStorageNotConfigured) || errors.Is(err, storage.ErrArchiveStorageNotSupported) { - logger.Info("Archive storage not created", zap.String("reason", err.Error())) - return nil, nil - } - if err != nil { - logger.Error("Cannot init archive storage reader", zap.Error(err)) - return nil, nil - } - writer, err := archiveFactory.CreateArchiveSpanWriter() - if errors.Is(err, storage.ErrArchiveStorageNotConfigured) || errors.Is(err, storage.ErrArchiveStorageNotSupported) { - logger.Info("Archive storage not created", zap.String("reason", err.Error())) - return nil, nil - } - if err != nil { - logger.Error("Cannot init archive storage writer", zap.Error(err)) - return nil, nil - } - - return NewTraceReader(reader), NewTraceWriter(writer) -} diff --git a/storage_v2/v1adapter/archive_test.go b/storage_v2/v1adapter/archive_test.go deleted file mode 100644 index 1dc1c854851..00000000000 --- a/storage_v2/v1adapter/archive_test.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2025 The Jaeger Authors. -// SPDX-License-Identifier: Apache-2.0 - -package v1adapter - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/zap" - - "github.com/jaegertracing/jaeger/pkg/metrics" - "github.com/jaegertracing/jaeger/storage" - "github.com/jaegertracing/jaeger/storage/dependencystore" - "github.com/jaegertracing/jaeger/storage/spanstore" - spanstoremocks "github.com/jaegertracing/jaeger/storage/spanstore/mocks" -) - -type fakeStorageFactory1 struct{} - -type fakeStorageFactory2 struct { - fakeStorageFactory1 - r spanstore.Reader - w spanstore.Writer - rErr error - wErr error -} - -func (*fakeStorageFactory1) Initialize(metrics.Factory, *zap.Logger) error { - return nil -} -func (*fakeStorageFactory1) CreateSpanReader() (spanstore.Reader, error) { return nil, nil } -func (*fakeStorageFactory1) CreateSpanWriter() (spanstore.Writer, error) { return nil, nil } -func (*fakeStorageFactory1) CreateDependencyReader() (dependencystore.Reader, error) { return nil, nil } - -func (f *fakeStorageFactory2) CreateArchiveSpanReader() (spanstore.Reader, error) { return f.r, f.rErr } -func (f *fakeStorageFactory2) CreateArchiveSpanWriter() (spanstore.Writer, error) { return f.w, f.wErr } - -var ( - _ storage.Factory = new(fakeStorageFactory1) - _ storage.ArchiveFactory = new(fakeStorageFactory2) -) - -func TestInitializeArchiveStorage(t *testing.T) { - tests := []struct { - name string - factory storage.Factory - wantReader spanstore.Reader - wantWriter spanstore.Writer - }{ - { - name: "Archive storage not supported by the factory", - factory: new(fakeStorageFactory1), - wantReader: nil, - wantWriter: nil, - }, - { - name: "Archive storage not configured", - factory: &fakeStorageFactory2{rErr: storage.ErrArchiveStorageNotConfigured}, - wantReader: nil, - wantWriter: nil, - }, - { - name: "Archive storage not supported for reader", - factory: &fakeStorageFactory2{rErr: storage.ErrArchiveStorageNotSupported}, - wantReader: nil, - wantWriter: nil, - }, - { - name: "Error initializing archive span reader", - factory: &fakeStorageFactory2{rErr: assert.AnError}, - wantReader: nil, - wantWriter: nil, - }, - { - name: "Archive storage not supported for writer", - factory: &fakeStorageFactory2{wErr: storage.ErrArchiveStorageNotSupported}, - wantReader: nil, - wantWriter: nil, - }, - { - name: "Error initializing archive span writer", - factory: &fakeStorageFactory2{wErr: assert.AnError}, - wantReader: nil, - wantWriter: nil, - }, - { - name: "Successfully initialize archive storage", - factory: &fakeStorageFactory2{r: &spanstoremocks.Reader{}, w: &spanstoremocks.Writer{}}, - wantReader: &spanstoremocks.Reader{}, - wantWriter: &spanstoremocks.Writer{}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - logger := zap.NewNop() - reader, writer := InitializeArchiveStorage(test.factory, logger) - if test.wantReader != nil { - require.Equal(t, test.wantReader, reader.spanReader) - } else { - require.Nil(t, reader) - } - if test.wantWriter != nil { - require.Equal(t, test.wantWriter, writer.spanWriter) - } else { - require.Nil(t, writer) - } - }) - } -}