From 804337ec79e544d92e5c97f78ff407c83200529e Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 7 Mar 2023 18:01:28 -0500 Subject: [PATCH 01/48] Add temporalite & temporaltest packages --- .../examples/helloworld/helloworld.go | 44 +++ .../examples/helloworld/testinterceptor.go | 61 ++++ temporal/internal/liteconfig/config.go | 244 ++++++++++++++++ temporal/internal/liteconfig/freeport.go | 56 ++++ temporal/temporalite/README.md | 22 ++ temporal/temporalite/options.go | 166 +++++++++++ temporal/temporalite/server.go | 178 ++++++++++++ temporal/temporaltest/README.md | 5 + temporal/temporaltest/logger.go | 38 +++ temporal/temporaltest/options.go | 67 +++++ temporal/temporaltest/server.go | 175 ++++++++++++ temporal/temporaltest/server_test.go | 265 ++++++++++++++++++ 12 files changed, 1321 insertions(+) create mode 100644 temporal/internal/examples/helloworld/helloworld.go create mode 100644 temporal/internal/examples/helloworld/testinterceptor.go create mode 100644 temporal/internal/liteconfig/config.go create mode 100644 temporal/internal/liteconfig/freeport.go create mode 100644 temporal/temporalite/README.md create mode 100644 temporal/temporalite/options.go create mode 100644 temporal/temporalite/server.go create mode 100644 temporal/temporaltest/README.md create mode 100644 temporal/temporaltest/logger.go create mode 100644 temporal/temporaltest/options.go create mode 100644 temporal/temporaltest/server.go create mode 100644 temporal/temporaltest/server_test.go diff --git a/temporal/internal/examples/helloworld/helloworld.go b/temporal/internal/examples/helloworld/helloworld.go new file mode 100644 index 00000000000..89d47cacd4d --- /dev/null +++ b/temporal/internal/examples/helloworld/helloworld.go @@ -0,0 +1,44 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. + +package helloworld + +import ( + "time" + + "context" + "fmt" + + "go.temporal.io/sdk/activity" + "go.temporal.io/sdk/worker" + "go.temporal.io/sdk/workflow" +) + +// Greet implements a Temporal workflow that returns a salutation for a given subject. +func Greet(ctx workflow.Context, subject string) (string, error) { + var greeting string + if err := workflow.ExecuteActivity( + workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ScheduleToCloseTimeout: time.Second}), + PickGreeting, + ).Get(ctx, &greeting); err != nil { + return "", err + } + + return fmt.Sprintf("%s %s", greeting, subject), nil +} + +// PickGreeting is a Temporal activity that returns some greeting text. +func PickGreeting(ctx context.Context) (string, error) { + return "Hello", nil +} + +func TestIntercept(ctx context.Context) (string, error) { + return "Ok", nil +} + +func RegisterWorkflowsAndActivities(r worker.Registry) { + r.RegisterWorkflow(Greet) + r.RegisterActivity(PickGreeting) + r.RegisterActivityWithOptions(TestIntercept, activity.RegisterOptions{Name: "TestIntercept"}) +} diff --git a/temporal/internal/examples/helloworld/testinterceptor.go b/temporal/internal/examples/helloworld/testinterceptor.go new file mode 100644 index 00000000000..4b3e782dbca --- /dev/null +++ b/temporal/internal/examples/helloworld/testinterceptor.go @@ -0,0 +1,61 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. + +package helloworld + +import ( + "time" + + "go.temporal.io/sdk/interceptor" + "go.temporal.io/sdk/workflow" +) + +var _ interceptor.Interceptor = &Interceptor{} + +type Interceptor struct { + interceptor.InterceptorBase +} + +type WorkflowInterceptor struct { + interceptor.WorkflowInboundInterceptorBase +} + +func NewTestInterceptor() *Interceptor { + return &Interceptor{} +} + +func (i *Interceptor) InterceptClient(next interceptor.ClientOutboundInterceptor) interceptor.ClientOutboundInterceptor { + return i.InterceptorBase.InterceptClient(next) +} + +func (i *Interceptor) InterceptWorkflow(ctx workflow.Context, next interceptor.WorkflowInboundInterceptor) interceptor.WorkflowInboundInterceptor { + return &WorkflowInterceptor{ + WorkflowInboundInterceptorBase: interceptor.WorkflowInboundInterceptorBase{ + Next: next, + }, + } +} + +func (i *WorkflowInterceptor) Init(outbound interceptor.WorkflowOutboundInterceptor) error { + return i.Next.Init(outbound) +} + +func (i *WorkflowInterceptor) ExecuteWorkflow(ctx workflow.Context, in *interceptor.ExecuteWorkflowInput) (interface{}, error) { + version := workflow.GetVersion(ctx, "version", workflow.DefaultVersion, 1) + var err error + + if version != workflow.DefaultVersion { + var vpt string + err = workflow.ExecuteLocalActivity( + workflow.WithLocalActivityOptions(ctx, workflow.LocalActivityOptions{ScheduleToCloseTimeout: time.Second}), + "TestIntercept", + ).Get(ctx, &vpt) + + if err != nil { + return nil, err + } + } + + return i.Next.ExecuteWorkflow(ctx, in) +} diff --git a/temporal/internal/liteconfig/config.go b/temporal/internal/liteconfig/config.go new file mode 100644 index 00000000000..e96fb54cf81 --- /dev/null +++ b/temporal/internal/liteconfig/config.go @@ -0,0 +1,244 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. + +package liteconfig + +import ( + "math/rand" + "os" + "time" + + "fmt" + "path/filepath" + "sort" + + "go.temporal.io/server/common/cluster" + "go.temporal.io/server/common/config" + "go.temporal.io/server/common/dynamicconfig" + "go.temporal.io/server/common/log" + "go.temporal.io/server/common/metrics" + "go.temporal.io/server/common/persistence/sql/sqlplugin/sqlite" + "go.temporal.io/server/temporal" +) + +const ( + broadcastAddress = "127.0.0.1" + defaultFrontendPort = 7233 +) + +// UIServer abstracts the github.com/temporalio/ui-server project to +// make it an optional import for programs that need web UI support. +// +// A working implementation of this interface is available here: +// https://pkg.go.dev/github.com/temporalio/ui-server/server#Server +type UIServer interface { + Start() error + Stop() +} + +type noopUIServer struct{} + +func (noopUIServer) Start() error { + return nil +} + +func (noopUIServer) Stop() {} + +type Config struct { + Ephemeral bool + DatabaseFilePath string + FrontendPort int + MetricsPort int + DynamicPorts bool + Namespaces []string + SQLitePragmas map[string]string + Logger log.Logger + ServerOptions []temporal.ServerOption + portProvider *PortProvider + FrontendIP string + UIServer UIServer + BaseConfig *config.Config + DynamicConfig dynamicconfig.StaticClient +} + +var SupportedPragmas = map[string]struct{}{ + "journal_mode": {}, + "synchronous": {}, +} + +func GetAllowedPragmas() []string { + var allowedPragmaList []string + for k := range SupportedPragmas { + allowedPragmaList = append(allowedPragmaList, k) + } + sort.Strings(allowedPragmaList) + return allowedPragmaList +} + +func NewDefaultConfig() (*Config, error) { + userConfigDir, err := os.UserConfigDir() + if err != nil { + return nil, fmt.Errorf("cannot determine user config directory: %w", err) + } + + return &Config{ + Ephemeral: false, + DatabaseFilePath: filepath.Join(userConfigDir, "temporalite", "db", "default.db"), + FrontendPort: 0, + MetricsPort: 0, + UIServer: noopUIServer{}, + DynamicPorts: false, + Namespaces: nil, + SQLitePragmas: nil, + Logger: log.NewZapLogger(log.BuildZapLogger(log.Config{ + Stdout: true, + Level: "info", + OutputFile: "", + })), + portProvider: NewPortProvider(), + FrontendIP: "", + BaseConfig: &config.Config{}, + }, nil +} + +func Convert(cfg *Config) *config.Config { + defer func() { + if err := cfg.portProvider.Close(); err != nil { + panic(err) + } + }() + + sqliteConfig := config.SQL{ + PluginName: sqlite.PluginName, + ConnectAttributes: make(map[string]string), + DatabaseName: cfg.DatabaseFilePath, + } + if cfg.Ephemeral { + sqliteConfig.ConnectAttributes["mode"] = "memory" + sqliteConfig.ConnectAttributes["cache"] = "shared" + sqliteConfig.DatabaseName = fmt.Sprintf("%d", rand.Intn(9999999)) + } else { + sqliteConfig.ConnectAttributes["mode"] = "rwc" + } + + for k, v := range cfg.SQLitePragmas { + sqliteConfig.ConnectAttributes["_"+k] = v + } + + var pprofPort int + if cfg.DynamicPorts { + if cfg.FrontendPort == 0 { + cfg.FrontendPort = cfg.portProvider.MustGetFreePort() + } + if cfg.MetricsPort == 0 { + cfg.MetricsPort = cfg.portProvider.MustGetFreePort() + } + pprofPort = cfg.portProvider.MustGetFreePort() + } else { + if cfg.FrontendPort == 0 { + cfg.FrontendPort = defaultFrontendPort + } + if cfg.MetricsPort == 0 { + cfg.MetricsPort = cfg.FrontendPort + 200 + } + pprofPort = cfg.FrontendPort + 201 + } + + baseConfig := cfg.BaseConfig + baseConfig.Global.Membership = config.Membership{ + MaxJoinDuration: 30 * time.Second, + BroadcastAddress: broadcastAddress, + } + baseConfig.Global.Metrics = &metrics.Config{ + Prometheus: &metrics.PrometheusConfig{ + ListenAddress: fmt.Sprintf("%s:%d", cfg.FrontendIP, cfg.MetricsPort), + HandlerPath: "/metrics", + }, + } + baseConfig.Global.PProf = config.PProf{Port: pprofPort} + baseConfig.Persistence = config.Persistence{ + DefaultStore: sqlite.PluginName, + VisibilityStore: sqlite.PluginName, + NumHistoryShards: 1, + DataStores: map[string]config.DataStore{ + sqlite.PluginName: {SQL: &sqliteConfig}, + }, + } + baseConfig.ClusterMetadata = &cluster.Config{ + EnableGlobalNamespace: false, + FailoverVersionIncrement: 10, + MasterClusterName: "active", + CurrentClusterName: "active", + ClusterInformation: map[string]cluster.ClusterInformation{ + "active": { + Enabled: true, + InitialFailoverVersion: 1, + RPCAddress: fmt.Sprintf("%s:%d", broadcastAddress, cfg.FrontendPort), + }, + }, + } + baseConfig.DCRedirectionPolicy = config.DCRedirectionPolicy{ + Policy: "noop", + } + baseConfig.Services = map[string]config.Service{ + "frontend": cfg.mustGetService(0), + "history": cfg.mustGetService(1), + "matching": cfg.mustGetService(2), + "worker": cfg.mustGetService(3), + } + baseConfig.Archival = config.Archival{ + History: config.HistoryArchival{ + State: "disabled", + EnableRead: false, + Provider: nil, + }, + Visibility: config.VisibilityArchival{ + State: "disabled", + EnableRead: false, + Provider: nil, + }, + } + baseConfig.PublicClient = config.PublicClient{ + HostPort: fmt.Sprintf("%s:%d", broadcastAddress, cfg.FrontendPort), + } + baseConfig.NamespaceDefaults = config.NamespaceDefaults{ + Archival: config.ArchivalNamespaceDefaults{ + History: config.HistoryArchivalNamespaceDefaults{ + State: "disabled", + }, + Visibility: config.VisibilityArchivalNamespaceDefaults{ + State: "disabled", + }, + }, + } + + return baseConfig +} + +func (cfg *Config) mustGetService(frontendPortOffset int) config.Service { + svc := config.Service{ + RPC: config.RPC{ + GRPCPort: cfg.FrontendPort + frontendPortOffset, + MembershipPort: cfg.FrontendPort + 100 + frontendPortOffset, + BindOnLocalHost: true, + BindOnIP: "", + }, + } + + // Assign any open port when configured to use dynamic ports + if cfg.DynamicPorts { + if frontendPortOffset != 0 { + svc.RPC.GRPCPort = cfg.portProvider.MustGetFreePort() + } + svc.RPC.MembershipPort = cfg.portProvider.MustGetFreePort() + } + + // Optionally bind frontend to IPv4 address + if frontendPortOffset == 0 && cfg.FrontendIP != "" { + svc.RPC.BindOnLocalHost = false + svc.RPC.BindOnIP = cfg.FrontendIP + } + + return svc +} diff --git a/temporal/internal/liteconfig/freeport.go b/temporal/internal/liteconfig/freeport.go new file mode 100644 index 00000000000..f3f077de2ef --- /dev/null +++ b/temporal/internal/liteconfig/freeport.go @@ -0,0 +1,56 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. + +package liteconfig + +import ( + "fmt" + "net" +) + +// Modified from https://github.com/phayes/freeport/blob/95f893ade6f232a5f1511d61735d89b1ae2df543/freeport.go + +func NewPortProvider() *PortProvider { + return &PortProvider{} +} + +type PortProvider struct { + listeners []*net.TCPListener +} + +// GetFreePort asks the kernel for a free open port that is ready to use. +func (p *PortProvider) GetFreePort() (int, error) { + addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") + if err != nil { + if addr, err = net.ResolveTCPAddr("tcp6", "[::1]:0"); err != nil { + panic(fmt.Sprintf("temporalite: failed to get free port: %v", err)) + } + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return 0, err + } + + p.listeners = append(p.listeners, l) + + return l.Addr().(*net.TCPAddr).Port, nil +} + +func (p *PortProvider) MustGetFreePort() int { + port, err := p.GetFreePort() + if err != nil { + panic(err) + } + return port +} + +func (p *PortProvider) Close() error { + for _, l := range p.listeners { + if err := l.Close(); err != nil { + return err + } + } + return nil +} diff --git a/temporal/temporalite/README.md b/temporal/temporalite/README.md new file mode 100644 index 00000000000..f3e16e3259d --- /dev/null +++ b/temporal/temporalite/README.md @@ -0,0 +1,22 @@ +# Temporalite + +> ⚠️ This package is currently experimental and not suitable for production use. ⚠️ + +Temporalite is a distribution of Temporal that runs as a single process with zero runtime dependencies. + +Persistence to disk and an in-memory mode are both supported via SQLite. + +## Why + +The goal of Temporalite is to make it simple and fast to run single-node Temporal servers for development, testing, and production use cases. + +Features that align with this goal: + +- High level library for configuring a SQLite backed Temporal server +- Fast startup time +- Minimal resource overhead: no dependencies on a container runtime or database server +- Support for Windows, Linux, and macOS + +## Backwards Compatability + +This package must not break backwards compatability in accordance with semantic versioning. diff --git a/temporal/temporalite/options.go b/temporal/temporalite/options.go new file mode 100644 index 00000000000..9efaf02c1ce --- /dev/null +++ b/temporal/temporalite/options.go @@ -0,0 +1,166 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. + +package temporalite + +import ( + "go.temporal.io/server/common/config" + "go.temporal.io/server/common/dynamicconfig" + "go.temporal.io/server/common/log" + "go.temporal.io/server/temporal" + "go.temporal.io/server/temporal/internal/liteconfig" +) + +// WithLogger overrides the default logger. +func WithLogger(logger log.Logger) ServerOption { + return newApplyFuncContainer(func(cfg *liteconfig.Config) { + cfg.Logger = logger + }) +} + +// WithDatabaseFilePath persists state to the file at the specified path. +func WithDatabaseFilePath(filepath string) ServerOption { + return newApplyFuncContainer(func(cfg *liteconfig.Config) { + cfg.Ephemeral = false + cfg.DatabaseFilePath = filepath + }) +} + +// WithPersistenceDisabled disables file persistence and uses the in-memory storage driver. +// State will be reset on each process restart. +func WithPersistenceDisabled() ServerOption { + return newApplyFuncContainer(func(cfg *liteconfig.Config) { + cfg.Ephemeral = true + }) +} + +// UIServer abstracts the github.com/temporalio/ui-server project to +// make it an optional import for programs that need web UI support. +// +// A working implementation of this interface is available here: +// https://pkg.go.dev/github.com/temporalio/ui-server/server#Server +type UIServer interface { + Start() error + Stop() +} + +// WithUI enables the Temporal web interface. +// +// When unspecified, Temporal will run in headless mode. +// +// This option accepts a UIServer implementation in order to avoid bloating +// programs that do not need to embed the UI. +// See ./cmd/temporalite/main.go for an example of usage. +func WithUI(server UIServer) ServerOption { + return newApplyFuncContainer(func(cfg *liteconfig.Config) { + cfg.UIServer = server + }) +} + +// WithFrontendPort sets the listening port for the temporal-frontend GRPC service. +// +// When unspecified, the default port number of 7233 is used. +func WithFrontendPort(port int) ServerOption { + return newApplyFuncContainer(func(cfg *liteconfig.Config) { + cfg.FrontendPort = port + }) +} + +// WithMetricsPort sets the listening port for metrics. +// +// When unspecified, the port will be system-chosen. +func WithMetricsPort(port int) ServerOption { + return newApplyFuncContainer(func(cfg *liteconfig.Config) { + cfg.MetricsPort = port + }) +} + +// WithFrontendIP binds the temporal-frontend GRPC service to a specific IP (eg. `0.0.0.0`) +// Check net.ParseIP for supported syntax; only IPv4 is supported. +// +// When unspecified, the frontend service will bind to localhost. +func WithFrontendIP(address string) ServerOption { + return newApplyFuncContainer(func(cfg *liteconfig.Config) { + cfg.FrontendIP = address + }) +} + +// WithDynamicPorts starts Temporal on system-chosen ports. +func WithDynamicPorts() ServerOption { + return newApplyFuncContainer(func(cfg *liteconfig.Config) { + cfg.DynamicPorts = true + }) +} + +// WithNamespaces registers each namespace on Temporal start. +func WithNamespaces(namespaces ...string) ServerOption { + return newApplyFuncContainer(func(cfg *liteconfig.Config) { + cfg.Namespaces = append(cfg.Namespaces, namespaces...) + }) +} + +// WithSQLitePragmas applies pragma statements to SQLite on Temporal start. +func WithSQLitePragmas(pragmas map[string]string) ServerOption { + return newApplyFuncContainer(func(cfg *liteconfig.Config) { + if cfg.SQLitePragmas == nil { + cfg.SQLitePragmas = make(map[string]string) + } + for k, v := range pragmas { + cfg.SQLitePragmas[k] = v + } + }) +} + +// WithOptions registers Temporal server options. +func WithOptions(options ...temporal.ServerOption) ServerOption { + return newApplyFuncContainer(func(cfg *liteconfig.Config) { + cfg.ServerOptions = append(cfg.ServerOptions, options...) + }) +} + +// WithBaseConfig sets the default Temporal server configuration. +// +// Storage and client configuration will always be overridden, however base config can be +// used to enable settings like TLS or authentication. +func WithBaseConfig(base *config.Config) ServerOption { + return newApplyFuncContainer(func(cfg *liteconfig.Config) { + cfg.BaseConfig = base + }) +} + +// WithDynamicConfigValue sets the given dynamic config key with the given set +// of values. This will overwrite a key if already set. +func WithDynamicConfigValue(key dynamicconfig.Key, value []dynamicconfig.ConstrainedValue) ServerOption { + return newApplyFuncContainer(func(cfg *liteconfig.Config) { + if cfg.DynamicConfig == nil { + cfg.DynamicConfig = dynamicconfig.StaticClient{} + } + cfg.DynamicConfig[key] = value + }) +} + +// WithSearchAttributeCacheDisabled forces refreshing the search attributes cache during each read operation. +// +// This effectively bypasses any cached value and is used to facilitate testing of changes in search attributes. +// This should not be turned on in production. +func WithSearchAttributeCacheDisabled() ServerOption { + return WithDynamicConfigValue( + dynamicconfig.ForceSearchAttributesCacheRefreshOnRead, + []dynamicconfig.ConstrainedValue{{Value: true}}, + ) +} + +type applyFuncContainer struct { + applyInternal func(*liteconfig.Config) +} + +func (fso *applyFuncContainer) apply(cfg *liteconfig.Config) { + fso.applyInternal(cfg) +} + +func newApplyFuncContainer(apply func(*liteconfig.Config)) *applyFuncContainer { + return &applyFuncContainer{ + applyInternal: apply, + } +} diff --git a/temporal/temporalite/server.go b/temporal/temporalite/server.go new file mode 100644 index 00000000000..940e67396ca --- /dev/null +++ b/temporal/temporalite/server.go @@ -0,0 +1,178 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. + +package temporalite + +import ( + "os" + "time" + + "context" + "fmt" + "path/filepath" + "strings" + + "go.temporal.io/sdk/client" + + "go.temporal.io/server/common/authorization" + "go.temporal.io/server/common/config" + sqliteplugin "go.temporal.io/server/common/persistence/sql/sqlplugin/sqlite" + "go.temporal.io/server/schema/sqlite" + "go.temporal.io/server/temporal" + "go.temporal.io/server/temporal/internal/liteconfig" +) + +// TemporaliteServer is a high level wrapper for temporal.Server that automatically configures a sqlite backend. +type TemporaliteServer struct { + internal temporal.Server + ui UIServer + frontendHostPort string + config *liteconfig.Config +} + +type ServerOption interface { + apply(*liteconfig.Config) +} + +// NewServer returns a TemporaliteServer with a sqlite backend. +func NewServer(opts ...ServerOption) (*TemporaliteServer, error) { + c, err := liteconfig.NewDefaultConfig() + if err != nil { + return nil, err + } + for _, opt := range opts { + opt.apply(c) + } + + for pragma := range c.SQLitePragmas { + if _, ok := liteconfig.SupportedPragmas[strings.ToLower(pragma)]; !ok { + return nil, fmt.Errorf("ERROR: unsupported pragma %q, %v allowed", pragma, liteconfig.GetAllowedPragmas()) + } + } + + cfg := liteconfig.Convert(c) + sqlConfig := cfg.Persistence.DataStores[sqliteplugin.PluginName].SQL + + if !c.Ephemeral { + // Apply migrations if file does not already exist + if _, err := os.Stat(c.DatabaseFilePath); os.IsNotExist(err) { + // Check if any of the parent dirs are missing + dir := filepath.Dir(c.DatabaseFilePath) + if _, err := os.Stat(dir); err != nil { + return nil, fmt.Errorf("error setting up schema: %w", err) + } + + if err := sqlite.SetupSchema(sqlConfig); err != nil { + return nil, fmt.Errorf("error setting up schema: %w", err) + } + } + } + // Pre-create namespaces + var namespaces []*sqlite.NamespaceConfig + for _, ns := range c.Namespaces { + namespaces = append(namespaces, sqlite.NewNamespaceConfig(cfg.ClusterMetadata.CurrentClusterName, ns, false)) + } + if err := sqlite.CreateNamespaces(sqlConfig, namespaces...); err != nil { + return nil, fmt.Errorf("error creating namespaces: %w", err) + } + + authorizer, err := authorization.GetAuthorizerFromConfig(&cfg.Global.Authorization) + if err != nil { + return nil, fmt.Errorf("unable to instantiate authorizer: %w", err) + } + + claimMapper, err := authorization.GetClaimMapperFromConfig(&cfg.Global.Authorization, c.Logger) + if err != nil { + return nil, fmt.Errorf("unable to instantiate claim mapper: %w", err) + } + + serverOpts := []temporal.ServerOption{ + temporal.WithConfig(cfg), + temporal.ForServices(temporal.DefaultServices), + temporal.WithLogger(c.Logger), + temporal.WithAuthorizer(authorizer), + temporal.WithClaimMapper(func(cfg *config.Config) authorization.ClaimMapper { + return claimMapper + }), + } + + if len(c.DynamicConfig) > 0 { + // To prevent having to code fall-through semantics right now, we currently + // eagerly fail if dynamic config is being configured in two ways + if cfg.DynamicConfigClient != nil { + return nil, fmt.Errorf("unable to have file-based dynamic config and individual dynamic config values") + } + serverOpts = append(serverOpts, temporal.WithDynamicConfigClient(c.DynamicConfig)) + } + + if len(c.ServerOptions) > 0 { + serverOpts = append(serverOpts, c.ServerOptions...) + } + + srv, err := temporal.NewServer(serverOpts...) + if err != nil { + return nil, fmt.Errorf("unable to instantiate server: %w", err) + } + + s := &TemporaliteServer{ + internal: srv, + ui: c.UIServer, + frontendHostPort: cfg.PublicClient.HostPort, + config: c, + } + + return s, nil +} + +// Start temporal server. +func (s *TemporaliteServer) Start() error { + go func() { + if err := s.ui.Start(); err != nil { + panic(err) + } + }() + return s.internal.Start() +} + +// Stop the server. +func (s *TemporaliteServer) Stop() { + if s == nil { + return + } + if s.ui != nil { + s.ui.Stop() + } + s.internal.Stop() +} + +// NewClient initializes a client ready to communicate with the Temporal +// server in the target namespace. +func (s *TemporaliteServer) NewClient(ctx context.Context, namespace string) (client.Client, error) { + return s.NewClientWithOptions(ctx, client.Options{Namespace: namespace}) +} + +// NewClientWithOptions is the same as NewClient but allows further customization. +// +// To set the client's namespace, use the corresponding field in client.Options. +// +// Note that the HostPort and ConnectionOptions fields of client.Options will always be overridden. +func (s *TemporaliteServer) NewClientWithOptions(ctx context.Context, options client.Options) (client.Client, error) { + options.HostPort = s.frontendHostPort + return client.NewClient(options) +} + +// FrontendHostPort returns the host:port for this server. +// +// When constructing a Temporalite client from within the same process, +// NewClient or NewClientWithOptions should be used instead. +func (s *TemporaliteServer) FrontendHostPort() string { + return s.frontendHostPort +} + +func timeoutFromContext(ctx context.Context, defaultTimeout time.Duration) time.Duration { + if deadline, ok := ctx.Deadline(); ok { + return deadline.Sub(time.Now()) + } + return defaultTimeout +} diff --git a/temporal/temporaltest/README.md b/temporal/temporaltest/README.md new file mode 100644 index 00000000000..d0e7c06e13b --- /dev/null +++ b/temporal/temporaltest/README.md @@ -0,0 +1,5 @@ +The `temporaltest` package provides helpers for writing end to end tests against a real Temporal server which can be run via the `go test` command. + +## Backwards Compatability + +This package must not break backwards compatability in accordance with semantic versioning. diff --git a/temporal/temporaltest/logger.go b/temporal/temporaltest/logger.go new file mode 100644 index 00000000000..2f57423e390 --- /dev/null +++ b/temporal/temporaltest/logger.go @@ -0,0 +1,38 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. + +package temporaltest + +import ( + "testing" +) + +type testLogger struct { + t *testing.T +} + +func (tl *testLogger) logLevel(lvl, msg string, keyvals ...interface{}) { + if tl.t == nil { + return + } + args := []interface{}{lvl, msg} + args = append(args, keyvals...) + tl.t.Log(args...) +} + +func (tl *testLogger) Debug(msg string, keyvals ...interface{}) { + tl.logLevel("DEBUG", msg, keyvals) +} + +func (tl *testLogger) Info(msg string, keyvals ...interface{}) { + tl.logLevel("INFO ", msg, keyvals) +} + +func (tl *testLogger) Warn(msg string, keyvals ...interface{}) { + tl.logLevel("WARN ", msg, keyvals) +} + +func (tl *testLogger) Error(msg string, keyvals ...interface{}) { + tl.logLevel("ERROR", msg, keyvals) +} diff --git a/temporal/temporaltest/options.go b/temporal/temporaltest/options.go new file mode 100644 index 00000000000..70e95d4866b --- /dev/null +++ b/temporal/temporaltest/options.go @@ -0,0 +1,67 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. + +package temporaltest + +import ( + "testing" + + "go.temporal.io/sdk/client" + "go.temporal.io/sdk/worker" + + "go.temporal.io/server/temporal/temporalite" +) + +type TestServerOption interface { + apply(*TestServer) +} + +// WithT directs all worker and client logs to the test logger. +// +// If this option is specified, then server will automatically be stopped when the +// test completes. +func WithT(t *testing.T) TestServerOption { + return newApplyFuncContainer(func(server *TestServer) { + server.t = t + }) +} + +// WithBaseClientOptions configures options for the default clients and workers connected to the test server. +func WithBaseClientOptions(o client.Options) TestServerOption { + return newApplyFuncContainer(func(server *TestServer) { + server.defaultClientOptions = o + }) +} + +// WithBaseWorkerOptions configures default options for workers connected to the test server. +// +// WorkflowPanicPolicy is always set to worker.FailWorkflow so that workflow executions +// fail fast when workflow code panics or detects non-determinism. +func WithBaseWorkerOptions(o worker.Options) TestServerOption { + o.WorkflowPanicPolicy = worker.FailWorkflow + return newApplyFuncContainer(func(server *TestServer) { + server.defaultWorkerOptions = o + }) +} + +// WithTemporaliteOptions provides the ability to use additional Temporalite options, including temporalite.WithUpstreamOptions. +func WithTemporaliteOptions(options ...temporalite.ServerOption) TestServerOption { + return newApplyFuncContainer(func(server *TestServer) { + server.serverOptions = append(server.serverOptions, options...) + }) +} + +type applyFuncContainer struct { + applyInternal func(*TestServer) +} + +func (fso *applyFuncContainer) apply(ts *TestServer) { + fso.applyInternal(ts) +} + +func newApplyFuncContainer(apply func(*TestServer)) *applyFuncContainer { + return &applyFuncContainer{ + applyInternal: apply, + } +} diff --git a/temporal/temporaltest/server.go b/temporal/temporaltest/server.go new file mode 100644 index 00000000000..0f145dbd080 --- /dev/null +++ b/temporal/temporaltest/server.go @@ -0,0 +1,175 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. + +// Package temporaltest provides utilities for end to end Temporal server testing. +package temporaltest + +import ( + "math/rand" + "testing" + "time" + + "context" + "fmt" + + "go.temporal.io/sdk/client" + "go.temporal.io/sdk/worker" + + "go.temporal.io/server/common/log" + + "go.temporal.io/server/temporal/temporalite" +) + +// A TestServer is a Temporal server listening on a system-chosen port on the +// local loopback interface, for use in end-to-end tests. +type TestServer struct { + server *temporalite.TemporaliteServer + defaultTestNamespace string + defaultClient client.Client + clients []client.Client + workers []worker.Worker + t *testing.T + defaultClientOptions client.Options + defaultWorkerOptions worker.Options + serverOptions []temporalite.ServerOption +} + +func (ts *TestServer) fatal(err error) { + if ts.t == nil { + panic(err) + } + ts.t.Fatal(err) +} + +// NewWorker registers and starts a Temporal worker on the specified task queue. +func (ts *TestServer) NewWorker(taskQueue string, registerFunc func(registry worker.Registry)) worker.Worker { + w := worker.New(ts.GetDefaultClient(), taskQueue, ts.defaultWorkerOptions) + registerFunc(w) + ts.workers = append(ts.workers, w) + + if err := w.Start(); err != nil { + ts.fatal(err) + } + + return w +} + +// NewWorkerWithOptions returns a Temporal worker on the specified task queue. +// +// WorkflowPanicPolicy is always set to worker.FailWorkflow so that workflow executions +// fail fast when workflow code panics or detects non-determinism. +func (ts *TestServer) NewWorkerWithOptions(taskQueue string, registerFunc func(registry worker.Registry), opts worker.Options) worker.Worker { + opts.WorkflowPanicPolicy = worker.FailWorkflow + + w := worker.New(ts.GetDefaultClient(), taskQueue, opts) + registerFunc(w) + ts.workers = append(ts.workers, w) + + if err := w.Start(); err != nil { + ts.fatal(err) + } + + return w +} + +// GetDefaultClient returns the default Temporal client configured for making requests to the server. +// +// It is configured to use a pre-registered test namespace and will be closed on TestServer.Stop. +func (ts *TestServer) GetDefaultClient() client.Client { + if ts.defaultClient == nil { + ts.defaultClient = ts.NewClientWithOptions(ts.defaultClientOptions) + } + return ts.defaultClient +} + +// GetDefaultNamespace returns the randomly generated namespace which has been pre-registered with the test server. +func (ts *TestServer) GetDefaultNamespace() string { + return ts.defaultTestNamespace +} + +// NewClientWithOptions returns a new Temporal client configured for making requests to the server. +// +// If no namespace option is set it will use a pre-registered test namespace. +// The returned client will be closed on TestServer.Stop. +func (ts *TestServer) NewClientWithOptions(opts client.Options) client.Client { + if opts.Namespace == "" { + opts.Namespace = ts.defaultTestNamespace + } + if opts.Logger == nil { + opts.Logger = &testLogger{ts.t} + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + c, err := ts.server.NewClientWithOptions(ctx, opts) + if err != nil { + ts.fatal(fmt.Errorf("error creating client: %w", err)) + } + + ts.clients = append(ts.clients, c) + + return c +} + +// Stop closes test clients and shuts down the server. +func (ts *TestServer) Stop() { + for _, w := range ts.workers { + w.Stop() + } + for _, c := range ts.clients { + c.Close() + } + ts.server.Stop() +} + +// NewServer starts and returns a new TestServer. +// +// If not specifying the WithT option, the caller should execute Stop when finished to close +// the server and release resources. +func NewServer(opts ...TestServerOption) *TestServer { + rand.Seed(time.Now().UnixNano()) + testNamespace := fmt.Sprintf("temporaltest-%d", rand.Intn(999999)) + + ts := TestServer{ + defaultTestNamespace: testNamespace, + } + + // Apply options + for _, opt := range opts { + opt.apply(&ts) + } + + if ts.t != nil { + ts.t.Cleanup(func() { + ts.Stop() + }) + } + + // Order of these options matters. When there are conflicts, options later in the list take precedence. + // Always specify options that are required for temporaltest last to avoid accidental overrides. + ts.serverOptions = append(ts.serverOptions, + temporalite.WithNamespaces(ts.defaultTestNamespace), + temporalite.WithPersistenceDisabled(), + temporalite.WithDynamicPorts(), + temporalite.WithLogger(log.NewNoopLogger()), + temporalite.WithSearchAttributeCacheDisabled(), + // Disable "accept incoming network connections?" prompt on macOS + temporalite.WithFrontendIP("127.0.0.1"), + ) + + s, err := temporalite.NewServer(ts.serverOptions...) + if err != nil { + ts.fatal(fmt.Errorf("error creating server: %w", err)) + } + ts.server = s + + go func() { + if err := s.Start(); err != nil { + ts.fatal(fmt.Errorf("error starting server: %w", err)) + } + }() + + return &ts +} diff --git a/temporal/temporaltest/server_test.go b/temporal/temporaltest/server_test.go new file mode 100644 index 00000000000..f0beab15ef8 --- /dev/null +++ b/temporal/temporaltest/server_test.go @@ -0,0 +1,265 @@ +// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// +// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. + +package temporaltest_test + +import ( + "testing" + "time" + + "context" + "fmt" + + "go.temporal.io/api/enums/v1" + "go.temporal.io/api/operatorservice/v1" + "go.temporal.io/sdk/client" + "go.temporal.io/sdk/worker" + + "go.temporal.io/server/temporal/internal/examples/helloworld" + "go.temporal.io/server/temporal/temporaltest" +) + +// to be used in example code +var t *testing.T + +func ExampleNewServer() { + // Create test Temporal server and client + ts := temporaltest.NewServer(temporaltest.WithT(t)) + c := ts.GetDefaultClient() + + // Register a new worker on the `hello_world` task queue + ts.NewWorker("hello_world", func(registry worker.Registry) { + helloworld.RegisterWorkflowsAndActivities(registry) + }) + + // Start test workflow + wfr, err := c.ExecuteWorkflow( + context.Background(), + client.StartWorkflowOptions{TaskQueue: "hello_world"}, + helloworld.Greet, + "world", + ) + if err != nil { + t.Fatal(err) + } + + // Get workflow result + var result string + if err := wfr.Get(context.Background(), &result); err != nil { + t.Fatal(err) + } + + // Print result + fmt.Println(result) + // Output: Hello world +} + +func TestNewServer(t *testing.T) { + ts := temporaltest.NewServer(temporaltest.WithT(t)) + + ts.NewWorker("hello_world", func(registry worker.Registry) { + helloworld.RegisterWorkflowsAndActivities(registry) + }) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + wfr, err := ts.GetDefaultClient().ExecuteWorkflow( + ctx, + client.StartWorkflowOptions{TaskQueue: "hello_world"}, + helloworld.Greet, + "world", + ) + if err != nil { + t.Fatal(err) + } + + var result string + if err := wfr.Get(ctx, &result); err != nil { + t.Fatal(err) + } + + if result != "Hello world" { + t.Fatalf("unexpected result: %q", result) + } +} + +func TestNewWorkerWithOptions(t *testing.T) { + ts := temporaltest.NewServer(temporaltest.WithT(t)) + + ts.NewWorkerWithOptions( + "hello_world", + func(registry worker.Registry) { + helloworld.RegisterWorkflowsAndActivities(registry) + }, + worker.Options{ + MaxConcurrentActivityExecutionSize: 1, + MaxConcurrentLocalActivityExecutionSize: 1, + }, + ) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + wfr, err := ts.GetDefaultClient().ExecuteWorkflow( + ctx, + client.StartWorkflowOptions{TaskQueue: "hello_world"}, + helloworld.Greet, + "world", + ) + if err != nil { + t.Fatal(err) + } + + var result string + if err := wfr.Get(ctx, &result); err != nil { + t.Fatal(err) + } + + if result != "Hello world" { + t.Fatalf("unexpected result: %q", result) + } + +} + +func TestDefaultWorkerOptions(t *testing.T) { + ts := temporaltest.NewServer( + temporaltest.WithT(t), + temporaltest.WithBaseWorkerOptions( + worker.Options{ + MaxConcurrentActivityExecutionSize: 1, + MaxConcurrentLocalActivityExecutionSize: 1, + }, + ), + ) + + ts.NewWorker("hello_world", func(registry worker.Registry) { + helloworld.RegisterWorkflowsAndActivities(registry) + }) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + wfr, err := ts.GetDefaultClient().ExecuteWorkflow( + ctx, + client.StartWorkflowOptions{TaskQueue: "hello_world"}, + helloworld.Greet, + "world", + ) + if err != nil { + t.Fatal(err) + } + + var result string + if err := wfr.Get(ctx, &result); err != nil { + t.Fatal(err) + } + + if result != "Hello world" { + t.Fatalf("unexpected result: %q", result) + } +} + +func TestClientWithCustomInterceptor(t *testing.T) { + var opts client.Options + opts.Interceptors = append(opts.Interceptors, helloworld.NewTestInterceptor()) + ts := temporaltest.NewServer( + temporaltest.WithT(t), + temporaltest.WithBaseClientOptions(opts), + ) + + ts.NewWorker( + "hello_world", + func(registry worker.Registry) { + helloworld.RegisterWorkflowsAndActivities(registry) + }, + ) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + wfr, err := ts.GetDefaultClient().ExecuteWorkflow( + ctx, + client.StartWorkflowOptions{TaskQueue: "hello_world"}, + helloworld.Greet, + "world", + ) + if err != nil { + t.Fatal(err) + } + + var result string + if err := wfr.Get(ctx, &result); err != nil { + t.Fatal(err) + } + + if result != "Hello world" { + t.Fatalf("unexpected result: %q", result) + } +} + +func TestSearchAttributeCacheDisabled(t *testing.T) { + // TODO(jlegrone) re-enable this test when advanced visibility is enabled in temporalite. + t.Skip("This test case does not currently pass as of the 1.20 release") + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + ts := temporaltest.NewServer(temporaltest.WithT(t)) + + testSearchAttr := "my-search-attr" + + // Create a search attribute + _, err := ts.GetDefaultClient().OperatorService().AddSearchAttributes(ctx, &operatorservice.AddSearchAttributesRequest{ + SearchAttributes: map[string]enums.IndexedValueType{ + testSearchAttr: enums.INDEXED_VALUE_TYPE_KEYWORD, + }, + Namespace: ts.GetDefaultNamespace(), + }) + if err != nil { + t.Fatal(err) + } + + // Confirm it exists immediately + resp, err := ts.GetDefaultClient().GetSearchAttributes(ctx) + if err != nil { + t.Fatal(err) + } + saType, ok := resp.GetKeys()[testSearchAttr] + if !ok { + t.Fatalf("search attribute %q is missing", testSearchAttr) + } + if saType != enums.INDEXED_VALUE_TYPE_KEYWORD { + t.Error("search attribute type does not match expected") + } +} + +func BenchmarkRunWorkflow(b *testing.B) { + ts := temporaltest.NewServer() + defer ts.Stop() + + ts.NewWorker("hello_world", func(registry worker.Registry) { + helloworld.RegisterWorkflowsAndActivities(registry) + }) + c := ts.GetDefaultClient() + + for i := 0; i < b.N; i++ { + func(b *testing.B) { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + wfr, err := c.ExecuteWorkflow( + ctx, + client.StartWorkflowOptions{TaskQueue: "hello_world"}, + helloworld.Greet, + "world", + ) + if err != nil { + b.Fatal(err) + } + + if err := wfr.Get(ctx, nil); err != nil { + b.Fatal(err) + } + }(b) + } +} From ceb57415c536ab5bcf536a5066b986c8290d59f9 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Fri, 10 Mar 2023 14:03:27 -0500 Subject: [PATCH 02/48] Move test workflow/interceptor into temoraltest package --- .../examples/helloworld/helloworld.go | 44 ------- .../examples/helloworld/testinterceptor.go | 61 ---------- temporal/temporaltest/server_test.go | 111 +++++++++++++++--- 3 files changed, 97 insertions(+), 119 deletions(-) delete mode 100644 temporal/internal/examples/helloworld/helloworld.go delete mode 100644 temporal/internal/examples/helloworld/testinterceptor.go diff --git a/temporal/internal/examples/helloworld/helloworld.go b/temporal/internal/examples/helloworld/helloworld.go deleted file mode 100644 index 89d47cacd4d..00000000000 --- a/temporal/internal/examples/helloworld/helloworld.go +++ /dev/null @@ -1,44 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. -// -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -package helloworld - -import ( - "time" - - "context" - "fmt" - - "go.temporal.io/sdk/activity" - "go.temporal.io/sdk/worker" - "go.temporal.io/sdk/workflow" -) - -// Greet implements a Temporal workflow that returns a salutation for a given subject. -func Greet(ctx workflow.Context, subject string) (string, error) { - var greeting string - if err := workflow.ExecuteActivity( - workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ScheduleToCloseTimeout: time.Second}), - PickGreeting, - ).Get(ctx, &greeting); err != nil { - return "", err - } - - return fmt.Sprintf("%s %s", greeting, subject), nil -} - -// PickGreeting is a Temporal activity that returns some greeting text. -func PickGreeting(ctx context.Context) (string, error) { - return "Hello", nil -} - -func TestIntercept(ctx context.Context) (string, error) { - return "Ok", nil -} - -func RegisterWorkflowsAndActivities(r worker.Registry) { - r.RegisterWorkflow(Greet) - r.RegisterActivity(PickGreeting) - r.RegisterActivityWithOptions(TestIntercept, activity.RegisterOptions{Name: "TestIntercept"}) -} diff --git a/temporal/internal/examples/helloworld/testinterceptor.go b/temporal/internal/examples/helloworld/testinterceptor.go deleted file mode 100644 index 4b3e782dbca..00000000000 --- a/temporal/internal/examples/helloworld/testinterceptor.go +++ /dev/null @@ -1,61 +0,0 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. -// -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. - -package helloworld - -import ( - "time" - - "go.temporal.io/sdk/interceptor" - "go.temporal.io/sdk/workflow" -) - -var _ interceptor.Interceptor = &Interceptor{} - -type Interceptor struct { - interceptor.InterceptorBase -} - -type WorkflowInterceptor struct { - interceptor.WorkflowInboundInterceptorBase -} - -func NewTestInterceptor() *Interceptor { - return &Interceptor{} -} - -func (i *Interceptor) InterceptClient(next interceptor.ClientOutboundInterceptor) interceptor.ClientOutboundInterceptor { - return i.InterceptorBase.InterceptClient(next) -} - -func (i *Interceptor) InterceptWorkflow(ctx workflow.Context, next interceptor.WorkflowInboundInterceptor) interceptor.WorkflowInboundInterceptor { - return &WorkflowInterceptor{ - WorkflowInboundInterceptorBase: interceptor.WorkflowInboundInterceptorBase{ - Next: next, - }, - } -} - -func (i *WorkflowInterceptor) Init(outbound interceptor.WorkflowOutboundInterceptor) error { - return i.Next.Init(outbound) -} - -func (i *WorkflowInterceptor) ExecuteWorkflow(ctx workflow.Context, in *interceptor.ExecuteWorkflowInput) (interface{}, error) { - version := workflow.GetVersion(ctx, "version", workflow.DefaultVersion, 1) - var err error - - if version != workflow.DefaultVersion { - var vpt string - err = workflow.ExecuteLocalActivity( - workflow.WithLocalActivityOptions(ctx, workflow.LocalActivityOptions{ScheduleToCloseTimeout: time.Second}), - "TestIntercept", - ).Get(ctx, &vpt) - - if err != nil { - return nil, err - } - } - - return i.Next.ExecuteWorkflow(ctx, in) -} diff --git a/temporal/temporaltest/server_test.go b/temporal/temporaltest/server_test.go index f0beab15ef8..0cf40b94439 100644 --- a/temporal/temporaltest/server_test.go +++ b/temporal/temporaltest/server_test.go @@ -13,10 +13,12 @@ import ( "go.temporal.io/api/enums/v1" "go.temporal.io/api/operatorservice/v1" + "go.temporal.io/sdk/activity" "go.temporal.io/sdk/client" + "go.temporal.io/sdk/interceptor" "go.temporal.io/sdk/worker" + "go.temporal.io/sdk/workflow" - "go.temporal.io/server/temporal/internal/examples/helloworld" "go.temporal.io/server/temporal/temporaltest" ) @@ -30,14 +32,14 @@ func ExampleNewServer() { // Register a new worker on the `hello_world` task queue ts.NewWorker("hello_world", func(registry worker.Registry) { - helloworld.RegisterWorkflowsAndActivities(registry) + RegisterWorkflowsAndActivities(registry) }) // Start test workflow wfr, err := c.ExecuteWorkflow( context.Background(), client.StartWorkflowOptions{TaskQueue: "hello_world"}, - helloworld.Greet, + Greet, "world", ) if err != nil { @@ -59,7 +61,7 @@ func TestNewServer(t *testing.T) { ts := temporaltest.NewServer(temporaltest.WithT(t)) ts.NewWorker("hello_world", func(registry worker.Registry) { - helloworld.RegisterWorkflowsAndActivities(registry) + RegisterWorkflowsAndActivities(registry) }) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) @@ -68,7 +70,7 @@ func TestNewServer(t *testing.T) { wfr, err := ts.GetDefaultClient().ExecuteWorkflow( ctx, client.StartWorkflowOptions{TaskQueue: "hello_world"}, - helloworld.Greet, + Greet, "world", ) if err != nil { @@ -91,7 +93,7 @@ func TestNewWorkerWithOptions(t *testing.T) { ts.NewWorkerWithOptions( "hello_world", func(registry worker.Registry) { - helloworld.RegisterWorkflowsAndActivities(registry) + RegisterWorkflowsAndActivities(registry) }, worker.Options{ MaxConcurrentActivityExecutionSize: 1, @@ -105,7 +107,7 @@ func TestNewWorkerWithOptions(t *testing.T) { wfr, err := ts.GetDefaultClient().ExecuteWorkflow( ctx, client.StartWorkflowOptions{TaskQueue: "hello_world"}, - helloworld.Greet, + Greet, "world", ) if err != nil { @@ -135,7 +137,7 @@ func TestDefaultWorkerOptions(t *testing.T) { ) ts.NewWorker("hello_world", func(registry worker.Registry) { - helloworld.RegisterWorkflowsAndActivities(registry) + RegisterWorkflowsAndActivities(registry) }) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() @@ -143,7 +145,7 @@ func TestDefaultWorkerOptions(t *testing.T) { wfr, err := ts.GetDefaultClient().ExecuteWorkflow( ctx, client.StartWorkflowOptions{TaskQueue: "hello_world"}, - helloworld.Greet, + Greet, "world", ) if err != nil { @@ -162,7 +164,7 @@ func TestDefaultWorkerOptions(t *testing.T) { func TestClientWithCustomInterceptor(t *testing.T) { var opts client.Options - opts.Interceptors = append(opts.Interceptors, helloworld.NewTestInterceptor()) + opts.Interceptors = append(opts.Interceptors, NewTestInterceptor()) ts := temporaltest.NewServer( temporaltest.WithT(t), temporaltest.WithBaseClientOptions(opts), @@ -171,7 +173,7 @@ func TestClientWithCustomInterceptor(t *testing.T) { ts.NewWorker( "hello_world", func(registry worker.Registry) { - helloworld.RegisterWorkflowsAndActivities(registry) + RegisterWorkflowsAndActivities(registry) }, ) @@ -181,7 +183,7 @@ func TestClientWithCustomInterceptor(t *testing.T) { wfr, err := ts.GetDefaultClient().ExecuteWorkflow( ctx, client.StartWorkflowOptions{TaskQueue: "hello_world"}, - helloworld.Greet, + Greet, "world", ) if err != nil { @@ -238,7 +240,7 @@ func BenchmarkRunWorkflow(b *testing.B) { defer ts.Stop() ts.NewWorker("hello_world", func(registry worker.Registry) { - helloworld.RegisterWorkflowsAndActivities(registry) + RegisterWorkflowsAndActivities(registry) }) c := ts.GetDefaultClient() @@ -250,7 +252,7 @@ func BenchmarkRunWorkflow(b *testing.B) { wfr, err := c.ExecuteWorkflow( ctx, client.StartWorkflowOptions{TaskQueue: "hello_world"}, - helloworld.Greet, + Greet, "world", ) if err != nil { @@ -263,3 +265,84 @@ func BenchmarkRunWorkflow(b *testing.B) { }(b) } } + +// Example workflow/activity + +// Greet implements a Temporal workflow that returns a salutation for a given subject. +func Greet(ctx workflow.Context, subject string) (string, error) { + var greeting string + if err := workflow.ExecuteActivity( + workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ScheduleToCloseTimeout: time.Second}), + PickGreeting, + ).Get(ctx, &greeting); err != nil { + return "", err + } + + return fmt.Sprintf("%s %s", greeting, subject), nil +} + +// PickGreeting is a Temporal activity that returns some greeting text. +func PickGreeting(ctx context.Context) (string, error) { + return "Hello", nil +} + +func HandleIntercept(ctx context.Context) (string, error) { + return "Ok", nil +} + +func RegisterWorkflowsAndActivities(r worker.Registry) { + r.RegisterWorkflow(Greet) + r.RegisterActivity(PickGreeting) + r.RegisterActivityWithOptions(HandleIntercept, activity.RegisterOptions{Name: "HandleIntercept"}) +} + +// Example interceptor + +var _ interceptor.Interceptor = &Interceptor{} + +type Interceptor struct { + interceptor.InterceptorBase +} + +type WorkflowInterceptor struct { + interceptor.WorkflowInboundInterceptorBase +} + +func NewTestInterceptor() *Interceptor { + return &Interceptor{} +} + +func (i *Interceptor) InterceptClient(next interceptor.ClientOutboundInterceptor) interceptor.ClientOutboundInterceptor { + return i.InterceptorBase.InterceptClient(next) +} + +func (i *Interceptor) InterceptWorkflow(ctx workflow.Context, next interceptor.WorkflowInboundInterceptor) interceptor.WorkflowInboundInterceptor { + return &WorkflowInterceptor{ + WorkflowInboundInterceptorBase: interceptor.WorkflowInboundInterceptorBase{ + Next: next, + }, + } +} + +func (i *WorkflowInterceptor) Init(outbound interceptor.WorkflowOutboundInterceptor) error { + return i.Next.Init(outbound) +} + +func (i *WorkflowInterceptor) ExecuteWorkflow(ctx workflow.Context, in *interceptor.ExecuteWorkflowInput) (interface{}, error) { + version := workflow.GetVersion(ctx, "version", workflow.DefaultVersion, 1) + var err error + + if version != workflow.DefaultVersion { + var vpt string + err = workflow.ExecuteLocalActivity( + workflow.WithLocalActivityOptions(ctx, workflow.LocalActivityOptions{ScheduleToCloseTimeout: time.Second}), + "HandleIntercept", + ).Get(ctx, &vpt) + + if err != nil { + return nil, err + } + } + + return i.Next.ExecuteWorkflow(ctx, in) +} From 9062c1966095edbd8f214aca86c3ecbe8c4c111b Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Fri, 10 Mar 2023 14:07:42 -0500 Subject: [PATCH 03/48] Move liteconfig into temporalite package --- .../liteconfig => temporalite}/config.go | 32 +++++++----------- .../liteconfig => temporalite}/freeport.go | 14 ++++---- temporal/temporalite/options.go | 33 +++++++++---------- temporal/temporalite/server.go | 13 ++++---- 4 files changed, 40 insertions(+), 52 deletions(-) rename temporal/{internal/liteconfig => temporalite}/config.go (88%) rename temporal/{internal/liteconfig => temporalite}/freeport.go (80%) diff --git a/temporal/internal/liteconfig/config.go b/temporal/temporalite/config.go similarity index 88% rename from temporal/internal/liteconfig/config.go rename to temporal/temporalite/config.go index e96fb54cf81..0bae9957ea4 100644 --- a/temporal/internal/liteconfig/config.go +++ b/temporal/temporalite/config.go @@ -2,7 +2,7 @@ // // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -package liteconfig +package temporalite import ( "math/rand" @@ -27,16 +27,6 @@ const ( defaultFrontendPort = 7233 ) -// UIServer abstracts the github.com/temporalio/ui-server project to -// make it an optional import for programs that need web UI support. -// -// A working implementation of this interface is available here: -// https://pkg.go.dev/github.com/temporalio/ui-server/server#Server -type UIServer interface { - Start() error - Stop() -} - type noopUIServer struct{} func (noopUIServer) Start() error { @@ -45,7 +35,7 @@ func (noopUIServer) Start() error { func (noopUIServer) Stop() {} -type Config struct { +type liteConfig struct { Ephemeral bool DatabaseFilePath string FrontendPort int @@ -55,34 +45,34 @@ type Config struct { SQLitePragmas map[string]string Logger log.Logger ServerOptions []temporal.ServerOption - portProvider *PortProvider + portProvider *portProvider FrontendIP string UIServer UIServer BaseConfig *config.Config DynamicConfig dynamicconfig.StaticClient } -var SupportedPragmas = map[string]struct{}{ +var supportedPragmas = map[string]struct{}{ "journal_mode": {}, "synchronous": {}, } -func GetAllowedPragmas() []string { +func getAllowedPragmas() []string { var allowedPragmaList []string - for k := range SupportedPragmas { + for k := range supportedPragmas { allowedPragmaList = append(allowedPragmaList, k) } sort.Strings(allowedPragmaList) return allowedPragmaList } -func NewDefaultConfig() (*Config, error) { +func newDefaultConfig() (*liteConfig, error) { userConfigDir, err := os.UserConfigDir() if err != nil { return nil, fmt.Errorf("cannot determine user config directory: %w", err) } - return &Config{ + return &liteConfig{ Ephemeral: false, DatabaseFilePath: filepath.Join(userConfigDir, "temporalite", "db", "default.db"), FrontendPort: 0, @@ -96,13 +86,13 @@ func NewDefaultConfig() (*Config, error) { Level: "info", OutputFile: "", })), - portProvider: NewPortProvider(), + portProvider: newPortProvider(), FrontendIP: "", BaseConfig: &config.Config{}, }, nil } -func Convert(cfg *Config) *config.Config { +func convertLiteConfig(cfg *liteConfig) *config.Config { defer func() { if err := cfg.portProvider.Close(); err != nil { panic(err) @@ -216,7 +206,7 @@ func Convert(cfg *Config) *config.Config { return baseConfig } -func (cfg *Config) mustGetService(frontendPortOffset int) config.Service { +func (cfg *liteConfig) mustGetService(frontendPortOffset int) config.Service { svc := config.Service{ RPC: config.RPC{ GRPCPort: cfg.FrontendPort + frontendPortOffset, diff --git a/temporal/internal/liteconfig/freeport.go b/temporal/temporalite/freeport.go similarity index 80% rename from temporal/internal/liteconfig/freeport.go rename to temporal/temporalite/freeport.go index f3f077de2ef..32ee40bb47e 100644 --- a/temporal/internal/liteconfig/freeport.go +++ b/temporal/temporalite/freeport.go @@ -2,7 +2,7 @@ // // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. -package liteconfig +package temporalite import ( "fmt" @@ -11,16 +11,16 @@ import ( // Modified from https://github.com/phayes/freeport/blob/95f893ade6f232a5f1511d61735d89b1ae2df543/freeport.go -func NewPortProvider() *PortProvider { - return &PortProvider{} +func newPortProvider() *portProvider { + return &portProvider{} } -type PortProvider struct { +type portProvider struct { listeners []*net.TCPListener } // GetFreePort asks the kernel for a free open port that is ready to use. -func (p *PortProvider) GetFreePort() (int, error) { +func (p *portProvider) GetFreePort() (int, error) { addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") if err != nil { if addr, err = net.ResolveTCPAddr("tcp6", "[::1]:0"); err != nil { @@ -38,7 +38,7 @@ func (p *PortProvider) GetFreePort() (int, error) { return l.Addr().(*net.TCPAddr).Port, nil } -func (p *PortProvider) MustGetFreePort() int { +func (p *portProvider) MustGetFreePort() int { port, err := p.GetFreePort() if err != nil { panic(err) @@ -46,7 +46,7 @@ func (p *PortProvider) MustGetFreePort() int { return port } -func (p *PortProvider) Close() error { +func (p *portProvider) Close() error { for _, l := range p.listeners { if err := l.Close(); err != nil { return err diff --git a/temporal/temporalite/options.go b/temporal/temporalite/options.go index 9efaf02c1ce..5648f6c9785 100644 --- a/temporal/temporalite/options.go +++ b/temporal/temporalite/options.go @@ -9,19 +9,18 @@ import ( "go.temporal.io/server/common/dynamicconfig" "go.temporal.io/server/common/log" "go.temporal.io/server/temporal" - "go.temporal.io/server/temporal/internal/liteconfig" ) // WithLogger overrides the default logger. func WithLogger(logger log.Logger) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *liteConfig) { cfg.Logger = logger }) } // WithDatabaseFilePath persists state to the file at the specified path. func WithDatabaseFilePath(filepath string) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *liteConfig) { cfg.Ephemeral = false cfg.DatabaseFilePath = filepath }) @@ -30,7 +29,7 @@ func WithDatabaseFilePath(filepath string) ServerOption { // WithPersistenceDisabled disables file persistence and uses the in-memory storage driver. // State will be reset on each process restart. func WithPersistenceDisabled() ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *liteConfig) { cfg.Ephemeral = true }) } @@ -53,7 +52,7 @@ type UIServer interface { // programs that do not need to embed the UI. // See ./cmd/temporalite/main.go for an example of usage. func WithUI(server UIServer) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *liteConfig) { cfg.UIServer = server }) } @@ -62,7 +61,7 @@ func WithUI(server UIServer) ServerOption { // // When unspecified, the default port number of 7233 is used. func WithFrontendPort(port int) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *liteConfig) { cfg.FrontendPort = port }) } @@ -71,7 +70,7 @@ func WithFrontendPort(port int) ServerOption { // // When unspecified, the port will be system-chosen. func WithMetricsPort(port int) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *liteConfig) { cfg.MetricsPort = port }) } @@ -81,28 +80,28 @@ func WithMetricsPort(port int) ServerOption { // // When unspecified, the frontend service will bind to localhost. func WithFrontendIP(address string) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *liteConfig) { cfg.FrontendIP = address }) } // WithDynamicPorts starts Temporal on system-chosen ports. func WithDynamicPorts() ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *liteConfig) { cfg.DynamicPorts = true }) } // WithNamespaces registers each namespace on Temporal start. func WithNamespaces(namespaces ...string) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *liteConfig) { cfg.Namespaces = append(cfg.Namespaces, namespaces...) }) } // WithSQLitePragmas applies pragma statements to SQLite on Temporal start. func WithSQLitePragmas(pragmas map[string]string) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *liteConfig) { if cfg.SQLitePragmas == nil { cfg.SQLitePragmas = make(map[string]string) } @@ -114,7 +113,7 @@ func WithSQLitePragmas(pragmas map[string]string) ServerOption { // WithOptions registers Temporal server options. func WithOptions(options ...temporal.ServerOption) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *liteConfig) { cfg.ServerOptions = append(cfg.ServerOptions, options...) }) } @@ -124,7 +123,7 @@ func WithOptions(options ...temporal.ServerOption) ServerOption { // Storage and client configuration will always be overridden, however base config can be // used to enable settings like TLS or authentication. func WithBaseConfig(base *config.Config) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *liteConfig) { cfg.BaseConfig = base }) } @@ -132,7 +131,7 @@ func WithBaseConfig(base *config.Config) ServerOption { // WithDynamicConfigValue sets the given dynamic config key with the given set // of values. This will overwrite a key if already set. func WithDynamicConfigValue(key dynamicconfig.Key, value []dynamicconfig.ConstrainedValue) ServerOption { - return newApplyFuncContainer(func(cfg *liteconfig.Config) { + return newApplyFuncContainer(func(cfg *liteConfig) { if cfg.DynamicConfig == nil { cfg.DynamicConfig = dynamicconfig.StaticClient{} } @@ -152,14 +151,14 @@ func WithSearchAttributeCacheDisabled() ServerOption { } type applyFuncContainer struct { - applyInternal func(*liteconfig.Config) + applyInternal func(*liteConfig) } -func (fso *applyFuncContainer) apply(cfg *liteconfig.Config) { +func (fso *applyFuncContainer) apply(cfg *liteConfig) { fso.applyInternal(cfg) } -func newApplyFuncContainer(apply func(*liteconfig.Config)) *applyFuncContainer { +func newApplyFuncContainer(apply func(*liteConfig)) *applyFuncContainer { return &applyFuncContainer{ applyInternal: apply, } diff --git a/temporal/temporalite/server.go b/temporal/temporalite/server.go index 940e67396ca..c3e5fc7621e 100644 --- a/temporal/temporalite/server.go +++ b/temporal/temporalite/server.go @@ -20,7 +20,6 @@ import ( sqliteplugin "go.temporal.io/server/common/persistence/sql/sqlplugin/sqlite" "go.temporal.io/server/schema/sqlite" "go.temporal.io/server/temporal" - "go.temporal.io/server/temporal/internal/liteconfig" ) // TemporaliteServer is a high level wrapper for temporal.Server that automatically configures a sqlite backend. @@ -28,16 +27,16 @@ type TemporaliteServer struct { internal temporal.Server ui UIServer frontendHostPort string - config *liteconfig.Config + config *liteConfig } type ServerOption interface { - apply(*liteconfig.Config) + apply(*liteConfig) } // NewServer returns a TemporaliteServer with a sqlite backend. func NewServer(opts ...ServerOption) (*TemporaliteServer, error) { - c, err := liteconfig.NewDefaultConfig() + c, err := newDefaultConfig() if err != nil { return nil, err } @@ -46,12 +45,12 @@ func NewServer(opts ...ServerOption) (*TemporaliteServer, error) { } for pragma := range c.SQLitePragmas { - if _, ok := liteconfig.SupportedPragmas[strings.ToLower(pragma)]; !ok { - return nil, fmt.Errorf("ERROR: unsupported pragma %q, %v allowed", pragma, liteconfig.GetAllowedPragmas()) + if _, ok := supportedPragmas[strings.ToLower(pragma)]; !ok { + return nil, fmt.Errorf("ERROR: unsupported pragma %q, %v allowed", pragma, getAllowedPragmas()) } } - cfg := liteconfig.Convert(c) + cfg := convertLiteConfig(c) sqlConfig := cfg.Persistence.DataStores[sqliteplugin.PluginName].SQL if !c.Ephemeral { From 9a527fdb8a53bfdd0dbb62e3397142b0d9abc99b Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Fri, 10 Mar 2023 14:14:20 -0500 Subject: [PATCH 04/48] Remove ui server --- temporal/temporalite/config.go | 10 ---------- temporal/temporalite/options.go | 23 ----------------------- temporal/temporalite/server.go | 10 ---------- 3 files changed, 43 deletions(-) diff --git a/temporal/temporalite/config.go b/temporal/temporalite/config.go index 0bae9957ea4..f052f5e858d 100644 --- a/temporal/temporalite/config.go +++ b/temporal/temporalite/config.go @@ -27,14 +27,6 @@ const ( defaultFrontendPort = 7233 ) -type noopUIServer struct{} - -func (noopUIServer) Start() error { - return nil -} - -func (noopUIServer) Stop() {} - type liteConfig struct { Ephemeral bool DatabaseFilePath string @@ -47,7 +39,6 @@ type liteConfig struct { ServerOptions []temporal.ServerOption portProvider *portProvider FrontendIP string - UIServer UIServer BaseConfig *config.Config DynamicConfig dynamicconfig.StaticClient } @@ -77,7 +68,6 @@ func newDefaultConfig() (*liteConfig, error) { DatabaseFilePath: filepath.Join(userConfigDir, "temporalite", "db", "default.db"), FrontendPort: 0, MetricsPort: 0, - UIServer: noopUIServer{}, DynamicPorts: false, Namespaces: nil, SQLitePragmas: nil, diff --git a/temporal/temporalite/options.go b/temporal/temporalite/options.go index 5648f6c9785..7819d904e38 100644 --- a/temporal/temporalite/options.go +++ b/temporal/temporalite/options.go @@ -34,29 +34,6 @@ func WithPersistenceDisabled() ServerOption { }) } -// UIServer abstracts the github.com/temporalio/ui-server project to -// make it an optional import for programs that need web UI support. -// -// A working implementation of this interface is available here: -// https://pkg.go.dev/github.com/temporalio/ui-server/server#Server -type UIServer interface { - Start() error - Stop() -} - -// WithUI enables the Temporal web interface. -// -// When unspecified, Temporal will run in headless mode. -// -// This option accepts a UIServer implementation in order to avoid bloating -// programs that do not need to embed the UI. -// See ./cmd/temporalite/main.go for an example of usage. -func WithUI(server UIServer) ServerOption { - return newApplyFuncContainer(func(cfg *liteConfig) { - cfg.UIServer = server - }) -} - // WithFrontendPort sets the listening port for the temporal-frontend GRPC service. // // When unspecified, the default port number of 7233 is used. diff --git a/temporal/temporalite/server.go b/temporal/temporalite/server.go index c3e5fc7621e..b1d4e8d546a 100644 --- a/temporal/temporalite/server.go +++ b/temporal/temporalite/server.go @@ -25,7 +25,6 @@ import ( // TemporaliteServer is a high level wrapper for temporal.Server that automatically configures a sqlite backend. type TemporaliteServer struct { internal temporal.Server - ui UIServer frontendHostPort string config *liteConfig } @@ -116,7 +115,6 @@ func NewServer(opts ...ServerOption) (*TemporaliteServer, error) { s := &TemporaliteServer{ internal: srv, - ui: c.UIServer, frontendHostPort: cfg.PublicClient.HostPort, config: c, } @@ -126,11 +124,6 @@ func NewServer(opts ...ServerOption) (*TemporaliteServer, error) { // Start temporal server. func (s *TemporaliteServer) Start() error { - go func() { - if err := s.ui.Start(); err != nil { - panic(err) - } - }() return s.internal.Start() } @@ -139,9 +132,6 @@ func (s *TemporaliteServer) Stop() { if s == nil { return } - if s.ui != nil { - s.ui.Stop() - } s.internal.Stop() } From b8fee4771c327590f91c981ffa18b744128eb166 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Fri, 10 Mar 2023 14:18:00 -0500 Subject: [PATCH 05/48] Remove unused timeoutFromContext func --- temporal/temporalite/server.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/temporal/temporalite/server.go b/temporal/temporalite/server.go index b1d4e8d546a..fba1a18f43b 100644 --- a/temporal/temporalite/server.go +++ b/temporal/temporalite/server.go @@ -6,7 +6,6 @@ package temporalite import ( "os" - "time" "context" "fmt" @@ -158,10 +157,3 @@ func (s *TemporaliteServer) NewClientWithOptions(ctx context.Context, options cl func (s *TemporaliteServer) FrontendHostPort() string { return s.frontendHostPort } - -func timeoutFromContext(ctx context.Context, defaultTimeout time.Duration) time.Duration { - if deadline, ok := ctx.Deadline(); ok { - return deadline.Sub(time.Now()) - } - return defaultTimeout -} From 53d73e154c3b66bc84db6d36b20cac6923dc13ff Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Fri, 10 Mar 2023 16:34:22 -0500 Subject: [PATCH 06/48] Avoid stuttering in temporalite package --- temporal/temporalite/config.go | 26 +++----------- temporal/temporalite/options.go | 61 ++++++++++++++++++++++++--------- temporal/temporalite/server.go | 24 ++++++------- temporal/temporaltest/server.go | 2 +- 4 files changed, 62 insertions(+), 51 deletions(-) diff --git a/temporal/temporalite/config.go b/temporal/temporalite/config.go index f052f5e858d..87f52d013f9 100644 --- a/temporal/temporalite/config.go +++ b/temporal/temporalite/config.go @@ -15,11 +15,9 @@ import ( "go.temporal.io/server/common/cluster" "go.temporal.io/server/common/config" - "go.temporal.io/server/common/dynamicconfig" "go.temporal.io/server/common/log" "go.temporal.io/server/common/metrics" "go.temporal.io/server/common/persistence/sql/sqlplugin/sqlite" - "go.temporal.io/server/temporal" ) const ( @@ -27,22 +25,6 @@ const ( defaultFrontendPort = 7233 ) -type liteConfig struct { - Ephemeral bool - DatabaseFilePath string - FrontendPort int - MetricsPort int - DynamicPorts bool - Namespaces []string - SQLitePragmas map[string]string - Logger log.Logger - ServerOptions []temporal.ServerOption - portProvider *portProvider - FrontendIP string - BaseConfig *config.Config - DynamicConfig dynamicconfig.StaticClient -} - var supportedPragmas = map[string]struct{}{ "journal_mode": {}, "synchronous": {}, @@ -57,13 +39,13 @@ func getAllowedPragmas() []string { return allowedPragmaList } -func newDefaultConfig() (*liteConfig, error) { +func newDefaultConfig() (*serverConfig, error) { userConfigDir, err := os.UserConfigDir() if err != nil { return nil, fmt.Errorf("cannot determine user config directory: %w", err) } - return &liteConfig{ + return &serverConfig{ Ephemeral: false, DatabaseFilePath: filepath.Join(userConfigDir, "temporalite", "db", "default.db"), FrontendPort: 0, @@ -82,7 +64,7 @@ func newDefaultConfig() (*liteConfig, error) { }, nil } -func convertLiteConfig(cfg *liteConfig) *config.Config { +func convertLiteConfig(cfg *serverConfig) *config.Config { defer func() { if err := cfg.portProvider.Close(); err != nil { panic(err) @@ -196,7 +178,7 @@ func convertLiteConfig(cfg *liteConfig) *config.Config { return baseConfig } -func (cfg *liteConfig) mustGetService(frontendPortOffset int) config.Service { +func (cfg *serverConfig) mustGetService(frontendPortOffset int) config.Service { svc := config.Service{ RPC: config.RPC{ GRPCPort: cfg.FrontendPort + frontendPortOffset, diff --git a/temporal/temporalite/options.go b/temporal/temporalite/options.go index 7819d904e38..f14a4847356 100644 --- a/temporal/temporalite/options.go +++ b/temporal/temporalite/options.go @@ -11,16 +11,45 @@ import ( "go.temporal.io/server/temporal" ) +type serverConfig struct { + // When true, Ephemeral disables file persistence and uses the in-memory storage driver. + // State will be reset on each process restart. + Ephemeral bool + // DatabaseFilePath persists state to the file at the specified path. + // + // This is required if Ephemeral is false. + DatabaseFilePath string + FrontendPort int + // WithMetricsPort sets the listening port for metrics. + // + // When unspecified, the port will be system-chosen. + MetricsPort int + DynamicPorts bool + Namespaces []string + SQLitePragmas map[string]string + // Logger overrides the default logger. + Logger log.Logger + ServerOptions []temporal.ServerOption + portProvider *portProvider + FrontendIP string + // BaseConfig sets the default Temporal server configuration. + // + // Storage and client configuration will always be overridden, however base config can be + // used to enable settings like TLS or authentication. + BaseConfig *config.Config + DynamicConfig dynamicconfig.StaticClient +} + // WithLogger overrides the default logger. func WithLogger(logger log.Logger) ServerOption { - return newApplyFuncContainer(func(cfg *liteConfig) { + return newApplyFuncContainer(func(cfg *serverConfig) { cfg.Logger = logger }) } // WithDatabaseFilePath persists state to the file at the specified path. func WithDatabaseFilePath(filepath string) ServerOption { - return newApplyFuncContainer(func(cfg *liteConfig) { + return newApplyFuncContainer(func(cfg *serverConfig) { cfg.Ephemeral = false cfg.DatabaseFilePath = filepath }) @@ -29,7 +58,7 @@ func WithDatabaseFilePath(filepath string) ServerOption { // WithPersistenceDisabled disables file persistence and uses the in-memory storage driver. // State will be reset on each process restart. func WithPersistenceDisabled() ServerOption { - return newApplyFuncContainer(func(cfg *liteConfig) { + return newApplyFuncContainer(func(cfg *serverConfig) { cfg.Ephemeral = true }) } @@ -38,7 +67,7 @@ func WithPersistenceDisabled() ServerOption { // // When unspecified, the default port number of 7233 is used. func WithFrontendPort(port int) ServerOption { - return newApplyFuncContainer(func(cfg *liteConfig) { + return newApplyFuncContainer(func(cfg *serverConfig) { cfg.FrontendPort = port }) } @@ -47,7 +76,7 @@ func WithFrontendPort(port int) ServerOption { // // When unspecified, the port will be system-chosen. func WithMetricsPort(port int) ServerOption { - return newApplyFuncContainer(func(cfg *liteConfig) { + return newApplyFuncContainer(func(cfg *serverConfig) { cfg.MetricsPort = port }) } @@ -57,28 +86,28 @@ func WithMetricsPort(port int) ServerOption { // // When unspecified, the frontend service will bind to localhost. func WithFrontendIP(address string) ServerOption { - return newApplyFuncContainer(func(cfg *liteConfig) { + return newApplyFuncContainer(func(cfg *serverConfig) { cfg.FrontendIP = address }) } // WithDynamicPorts starts Temporal on system-chosen ports. -func WithDynamicPorts() ServerOption { - return newApplyFuncContainer(func(cfg *liteConfig) { +func WithDynamicPorts() Option { + return newApplyFuncContainer(func(cfg *temporaliteConfig) { cfg.DynamicPorts = true }) } // WithNamespaces registers each namespace on Temporal start. func WithNamespaces(namespaces ...string) ServerOption { - return newApplyFuncContainer(func(cfg *liteConfig) { + return newApplyFuncContainer(func(cfg *serverConfig) { cfg.Namespaces = append(cfg.Namespaces, namespaces...) }) } // WithSQLitePragmas applies pragma statements to SQLite on Temporal start. func WithSQLitePragmas(pragmas map[string]string) ServerOption { - return newApplyFuncContainer(func(cfg *liteConfig) { + return newApplyFuncContainer(func(cfg *serverConfig) { if cfg.SQLitePragmas == nil { cfg.SQLitePragmas = make(map[string]string) } @@ -90,7 +119,7 @@ func WithSQLitePragmas(pragmas map[string]string) ServerOption { // WithOptions registers Temporal server options. func WithOptions(options ...temporal.ServerOption) ServerOption { - return newApplyFuncContainer(func(cfg *liteConfig) { + return newApplyFuncContainer(func(cfg *serverConfig) { cfg.ServerOptions = append(cfg.ServerOptions, options...) }) } @@ -100,7 +129,7 @@ func WithOptions(options ...temporal.ServerOption) ServerOption { // Storage and client configuration will always be overridden, however base config can be // used to enable settings like TLS or authentication. func WithBaseConfig(base *config.Config) ServerOption { - return newApplyFuncContainer(func(cfg *liteConfig) { + return newApplyFuncContainer(func(cfg *serverConfig) { cfg.BaseConfig = base }) } @@ -108,7 +137,7 @@ func WithBaseConfig(base *config.Config) ServerOption { // WithDynamicConfigValue sets the given dynamic config key with the given set // of values. This will overwrite a key if already set. func WithDynamicConfigValue(key dynamicconfig.Key, value []dynamicconfig.ConstrainedValue) ServerOption { - return newApplyFuncContainer(func(cfg *liteConfig) { + return newApplyFuncContainer(func(cfg *serverConfig) { if cfg.DynamicConfig == nil { cfg.DynamicConfig = dynamicconfig.StaticClient{} } @@ -128,14 +157,14 @@ func WithSearchAttributeCacheDisabled() ServerOption { } type applyFuncContainer struct { - applyInternal func(*liteConfig) + applyInternal func(*serverConfig) } -func (fso *applyFuncContainer) apply(cfg *liteConfig) { +func (fso *applyFuncContainer) apply(cfg *serverConfig) { fso.applyInternal(cfg) } -func newApplyFuncContainer(apply func(*liteConfig)) *applyFuncContainer { +func newApplyFuncContainer(apply func(*serverConfig)) *applyFuncContainer { return &applyFuncContainer{ applyInternal: apply, } diff --git a/temporal/temporalite/server.go b/temporal/temporalite/server.go index fba1a18f43b..f40a4f47a31 100644 --- a/temporal/temporalite/server.go +++ b/temporal/temporalite/server.go @@ -21,19 +21,19 @@ import ( "go.temporal.io/server/temporal" ) -// TemporaliteServer is a high level wrapper for temporal.Server that automatically configures a sqlite backend. -type TemporaliteServer struct { +// Server is a high level wrapper for temporal.Server that automatically configures a sqlite backend. +type Server struct { internal temporal.Server frontendHostPort string - config *liteConfig + config *serverConfig } type ServerOption interface { - apply(*liteConfig) + apply(*serverConfig) } -// NewServer returns a TemporaliteServer with a sqlite backend. -func NewServer(opts ...ServerOption) (*TemporaliteServer, error) { +// NewServer returns a Server with a sqlite backend. +func NewServer(opts ...ServerOption) (*Server, error) { c, err := newDefaultConfig() if err != nil { return nil, err @@ -112,7 +112,7 @@ func NewServer(opts ...ServerOption) (*TemporaliteServer, error) { return nil, fmt.Errorf("unable to instantiate server: %w", err) } - s := &TemporaliteServer{ + s := &Server{ internal: srv, frontendHostPort: cfg.PublicClient.HostPort, config: c, @@ -122,12 +122,12 @@ func NewServer(opts ...ServerOption) (*TemporaliteServer, error) { } // Start temporal server. -func (s *TemporaliteServer) Start() error { +func (s *Server) Start() error { return s.internal.Start() } // Stop the server. -func (s *TemporaliteServer) Stop() { +func (s *Server) Stop() { if s == nil { return } @@ -136,7 +136,7 @@ func (s *TemporaliteServer) Stop() { // NewClient initializes a client ready to communicate with the Temporal // server in the target namespace. -func (s *TemporaliteServer) NewClient(ctx context.Context, namespace string) (client.Client, error) { +func (s *Server) NewClient(ctx context.Context, namespace string) (client.Client, error) { return s.NewClientWithOptions(ctx, client.Options{Namespace: namespace}) } @@ -145,7 +145,7 @@ func (s *TemporaliteServer) NewClient(ctx context.Context, namespace string) (cl // To set the client's namespace, use the corresponding field in client.Options. // // Note that the HostPort and ConnectionOptions fields of client.Options will always be overridden. -func (s *TemporaliteServer) NewClientWithOptions(ctx context.Context, options client.Options) (client.Client, error) { +func (s *Server) NewClientWithOptions(ctx context.Context, options client.Options) (client.Client, error) { options.HostPort = s.frontendHostPort return client.NewClient(options) } @@ -154,6 +154,6 @@ func (s *TemporaliteServer) NewClientWithOptions(ctx context.Context, options cl // // When constructing a Temporalite client from within the same process, // NewClient or NewClientWithOptions should be used instead. -func (s *TemporaliteServer) FrontendHostPort() string { +func (s *Server) FrontendHostPort() string { return s.frontendHostPort } diff --git a/temporal/temporaltest/server.go b/temporal/temporaltest/server.go index 0f145dbd080..4920bd8ccb2 100644 --- a/temporal/temporaltest/server.go +++ b/temporal/temporaltest/server.go @@ -24,7 +24,7 @@ import ( // A TestServer is a Temporal server listening on a system-chosen port on the // local loopback interface, for use in end-to-end tests. type TestServer struct { - server *temporalite.TemporaliteServer + server *temporalite.Server defaultTestNamespace string defaultClient client.Client clients []client.Client From b5e748747e214c2b2b0383098d210d15eecebfe0 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Fri, 10 Mar 2023 16:40:50 -0500 Subject: [PATCH 07/48] Always use dynamic ports --- temporal/temporalite/config.go | 32 +++++++++----------------------- temporal/temporalite/options.go | 8 -------- temporal/temporaltest/server.go | 1 - 3 files changed, 9 insertions(+), 32 deletions(-) diff --git a/temporal/temporalite/config.go b/temporal/temporalite/config.go index 87f52d013f9..c137a2a6f88 100644 --- a/temporal/temporalite/config.go +++ b/temporal/temporalite/config.go @@ -50,7 +50,6 @@ func newDefaultConfig() (*serverConfig, error) { DatabaseFilePath: filepath.Join(userConfigDir, "temporalite", "db", "default.db"), FrontendPort: 0, MetricsPort: 0, - DynamicPorts: false, Namespaces: nil, SQLitePragmas: nil, Logger: log.NewZapLogger(log.BuildZapLogger(log.Config{ @@ -88,24 +87,13 @@ func convertLiteConfig(cfg *serverConfig) *config.Config { sqliteConfig.ConnectAttributes["_"+k] = v } - var pprofPort int - if cfg.DynamicPorts { - if cfg.FrontendPort == 0 { - cfg.FrontendPort = cfg.portProvider.MustGetFreePort() - } - if cfg.MetricsPort == 0 { - cfg.MetricsPort = cfg.portProvider.MustGetFreePort() - } - pprofPort = cfg.portProvider.MustGetFreePort() - } else { - if cfg.FrontendPort == 0 { - cfg.FrontendPort = defaultFrontendPort - } - if cfg.MetricsPort == 0 { - cfg.MetricsPort = cfg.FrontendPort + 200 - } - pprofPort = cfg.FrontendPort + 201 + if cfg.FrontendPort == 0 { + cfg.FrontendPort = cfg.portProvider.MustGetFreePort() + } + if cfg.MetricsPort == 0 { + cfg.MetricsPort = cfg.portProvider.MustGetFreePort() } + pprofPort := cfg.portProvider.MustGetFreePort() baseConfig := cfg.BaseConfig baseConfig.Global.Membership = config.Membership{ @@ -189,12 +177,10 @@ func (cfg *serverConfig) mustGetService(frontendPortOffset int) config.Service { } // Assign any open port when configured to use dynamic ports - if cfg.DynamicPorts { - if frontendPortOffset != 0 { - svc.RPC.GRPCPort = cfg.portProvider.MustGetFreePort() - } - svc.RPC.MembershipPort = cfg.portProvider.MustGetFreePort() + if frontendPortOffset != 0 { + svc.RPC.GRPCPort = cfg.portProvider.MustGetFreePort() } + svc.RPC.MembershipPort = cfg.portProvider.MustGetFreePort() // Optionally bind frontend to IPv4 address if frontendPortOffset == 0 && cfg.FrontendIP != "" { diff --git a/temporal/temporalite/options.go b/temporal/temporalite/options.go index f14a4847356..fb93a5eff70 100644 --- a/temporal/temporalite/options.go +++ b/temporal/temporalite/options.go @@ -24,7 +24,6 @@ type serverConfig struct { // // When unspecified, the port will be system-chosen. MetricsPort int - DynamicPorts bool Namespaces []string SQLitePragmas map[string]string // Logger overrides the default logger. @@ -91,13 +90,6 @@ func WithFrontendIP(address string) ServerOption { }) } -// WithDynamicPorts starts Temporal on system-chosen ports. -func WithDynamicPorts() Option { - return newApplyFuncContainer(func(cfg *temporaliteConfig) { - cfg.DynamicPorts = true - }) -} - // WithNamespaces registers each namespace on Temporal start. func WithNamespaces(namespaces ...string) ServerOption { return newApplyFuncContainer(func(cfg *serverConfig) { diff --git a/temporal/temporaltest/server.go b/temporal/temporaltest/server.go index 4920bd8ccb2..836567535cb 100644 --- a/temporal/temporaltest/server.go +++ b/temporal/temporaltest/server.go @@ -152,7 +152,6 @@ func NewServer(opts ...TestServerOption) *TestServer { ts.serverOptions = append(ts.serverOptions, temporalite.WithNamespaces(ts.defaultTestNamespace), temporalite.WithPersistenceDisabled(), - temporalite.WithDynamicPorts(), temporalite.WithLogger(log.NewNoopLogger()), temporalite.WithSearchAttributeCacheDisabled(), // Disable "accept incoming network connections?" prompt on macOS From 3f2d1e5ea81789107c54868b18749601d133a327 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Fri, 10 Mar 2023 16:54:33 -0500 Subject: [PATCH 08/48] Add additional config doc comments --- temporal/temporalite/options.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/temporal/temporalite/options.go b/temporal/temporalite/options.go index fb93a5eff70..d607a9a96fe 100644 --- a/temporal/temporalite/options.go +++ b/temporal/temporalite/options.go @@ -23,11 +23,14 @@ type serverConfig struct { // WithMetricsPort sets the listening port for metrics. // // When unspecified, the port will be system-chosen. - MetricsPort int - Namespaces []string + MetricsPort int + // Namespaces specified here will be automatically registered on Temporal start. + Namespaces []string + // SQLitePragmas specified here will be applied as pragma statements to SQLite on Temporal start. SQLitePragmas map[string]string // Logger overrides the default logger. - Logger log.Logger + Logger log.Logger + // ServerOptions to be applied on Temporal start. ServerOptions []temporal.ServerOption portProvider *portProvider FrontendIP string @@ -35,7 +38,8 @@ type serverConfig struct { // // Storage and client configuration will always be overridden, however base config can be // used to enable settings like TLS or authentication. - BaseConfig *config.Config + BaseConfig *config.Config + // DynamicConfig sets dynamic config values used by the server. DynamicConfig dynamicconfig.StaticClient } From 73dacd0dde61abc2b7630c587486769712d37862 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Fri, 17 Mar 2023 15:22:07 -0400 Subject: [PATCH 09/48] Update copyright --- temporal/temporalite/config.go | 26 ++++++++++++++++++++++++-- temporal/temporalite/freeport.go | 26 ++++++++++++++++++++++++-- temporal/temporalite/options.go | 26 ++++++++++++++++++++++++-- temporal/temporalite/server.go | 26 ++++++++++++++++++++++++-- temporal/temporaltest/logger.go | 26 ++++++++++++++++++++++++-- temporal/temporaltest/options.go | 26 ++++++++++++++++++++++++-- temporal/temporaltest/server.go | 26 ++++++++++++++++++++++++-- temporal/temporaltest/server_test.go | 26 ++++++++++++++++++++++++-- 8 files changed, 192 insertions(+), 16 deletions(-) diff --git a/temporal/temporalite/config.go b/temporal/temporalite/config.go index c137a2a6f88..f3826488a68 100644 --- a/temporal/temporalite/config.go +++ b/temporal/temporalite/config.go @@ -1,6 +1,28 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// The MIT License // -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. +// Copyright (c) 2021 Datadog, Inc. +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. package temporalite diff --git a/temporal/temporalite/freeport.go b/temporal/temporalite/freeport.go index 32ee40bb47e..6f40bc97f0a 100644 --- a/temporal/temporalite/freeport.go +++ b/temporal/temporalite/freeport.go @@ -1,6 +1,28 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// The MIT License // -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. +// Copyright (c) 2021 Datadog, Inc. +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. package temporalite diff --git a/temporal/temporalite/options.go b/temporal/temporalite/options.go index d607a9a96fe..895558b763c 100644 --- a/temporal/temporalite/options.go +++ b/temporal/temporalite/options.go @@ -1,6 +1,28 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// The MIT License // -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. +// Copyright (c) 2021 Datadog, Inc. +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. package temporalite diff --git a/temporal/temporalite/server.go b/temporal/temporalite/server.go index f40a4f47a31..1657ebcf111 100644 --- a/temporal/temporalite/server.go +++ b/temporal/temporalite/server.go @@ -1,6 +1,28 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// The MIT License // -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. +// Copyright (c) 2021 Datadog, Inc. +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. package temporalite diff --git a/temporal/temporaltest/logger.go b/temporal/temporaltest/logger.go index 2f57423e390..c9aa5b426f0 100644 --- a/temporal/temporaltest/logger.go +++ b/temporal/temporaltest/logger.go @@ -1,6 +1,28 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// The MIT License // -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. +// Copyright (c) 2021 Datadog, Inc. +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. package temporaltest diff --git a/temporal/temporaltest/options.go b/temporal/temporaltest/options.go index 70e95d4866b..0cd28a7af2f 100644 --- a/temporal/temporaltest/options.go +++ b/temporal/temporaltest/options.go @@ -1,6 +1,28 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// The MIT License // -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. +// Copyright (c) 2021 Datadog, Inc. +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. package temporaltest diff --git a/temporal/temporaltest/server.go b/temporal/temporaltest/server.go index 836567535cb..7e4c821a36d 100644 --- a/temporal/temporaltest/server.go +++ b/temporal/temporaltest/server.go @@ -1,6 +1,28 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// The MIT License // -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. +// Copyright (c) 2021 Datadog, Inc. +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. // Package temporaltest provides utilities for end to end Temporal server testing. package temporaltest diff --git a/temporal/temporaltest/server_test.go b/temporal/temporaltest/server_test.go index 0cf40b94439..811ea400430 100644 --- a/temporal/temporaltest/server_test.go +++ b/temporal/temporaltest/server_test.go @@ -1,6 +1,28 @@ -// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License. +// The MIT License // -// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2021 Datadog, Inc. +// Copyright (c) 2021 Datadog, Inc. +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. package temporaltest_test From 2ca1af6353443aa162fc9ff5ff5c9d34c3d7c682 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Mon, 20 Mar 2023 22:11:56 -0400 Subject: [PATCH 10/48] Refactor temporalite into temporal package --- temporal/{temporalite => }/freeport.go | 2 +- temporal/lite_server.go | 346 +++++++++++++++++++++++++ temporal/temporalite/README.md | 22 -- temporal/temporalite/config.go | 214 --------------- temporal/temporalite/options.go | 189 -------------- temporal/temporalite/server.go | 181 ------------- temporal/temporaltest/options.go | 7 +- temporal/temporaltest/server.go | 28 +- 8 files changed, 363 insertions(+), 626 deletions(-) rename temporal/{temporalite => }/freeport.go (99%) create mode 100644 temporal/lite_server.go delete mode 100644 temporal/temporalite/README.md delete mode 100644 temporal/temporalite/config.go delete mode 100644 temporal/temporalite/options.go delete mode 100644 temporal/temporalite/server.go diff --git a/temporal/temporalite/freeport.go b/temporal/freeport.go similarity index 99% rename from temporal/temporalite/freeport.go rename to temporal/freeport.go index 6f40bc97f0a..e02a8384586 100644 --- a/temporal/temporalite/freeport.go +++ b/temporal/freeport.go @@ -24,7 +24,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package temporalite +package temporal import ( "fmt" diff --git a/temporal/lite_server.go b/temporal/lite_server.go new file mode 100644 index 00000000000..4a94c2239e1 --- /dev/null +++ b/temporal/lite_server.go @@ -0,0 +1,346 @@ +package temporal + +import ( + "context" + "fmt" + "math/rand" + "os" + "path/filepath" + "sort" + "strings" + "time" + + "go.temporal.io/sdk/client" + "go.temporal.io/server/common/authorization" + "go.temporal.io/server/common/cluster" + "go.temporal.io/server/common/config" + "go.temporal.io/server/common/dynamicconfig" + "go.temporal.io/server/common/log" + "go.temporal.io/server/common/metrics" + sqliteplugin "go.temporal.io/server/common/persistence/sql/sqlplugin/sqlite" + "go.temporal.io/server/schema/sqlite" +) + +const localBroadcastAddress = "127.0.0.1" + +type LiteServerConfig struct { + // When true, Ephemeral disables file persistence and uses the in-memory storage driver. + // State will be reset on each process restart. + Ephemeral bool + // DatabaseFilePath persists state to the file at the specified path. + // + // This is required if Ephemeral is false. + DatabaseFilePath string + // Address on which frontend service should listen. + FrontendIP string + // Port on which frontend service should listen. + FrontendPort int + // WithMetricsPort sets the listening port for metrics. + // + // When unspecified, the port will be system-chosen. + MetricsPort int + // Namespaces specified here will be automatically registered on Temporal start. + Namespaces []string + // SQLitePragmas specified here will be applied as pragma statements to SQLite on Temporal start. + SQLitePragmas map[string]string + // Logger overrides the default logger. + Logger log.Logger + // BaseConfig sets the default Temporal server configuration. + // + // Storage and client configuration will always be overridden, however base config can be + // used to enable settings like TLS or authentication. + BaseConfig *config.Config + // DynamicConfig sets dynamic config values used by the server. + DynamicConfig dynamicconfig.StaticClient +} + +func (cfg *LiteServerConfig) apply(serverConfig *config.Config, provider *portProvider) { + sqliteConfig := config.SQL{ + PluginName: sqliteplugin.PluginName, + ConnectAttributes: make(map[string]string), + DatabaseName: cfg.DatabaseFilePath, + } + if cfg.Ephemeral { + sqliteConfig.ConnectAttributes["mode"] = "memory" + sqliteConfig.ConnectAttributes["cache"] = "shared" + sqliteConfig.DatabaseName = fmt.Sprintf("%d", rand.Intn(9999999)) + } else { + sqliteConfig.ConnectAttributes["mode"] = "rwc" + } + + for k, v := range cfg.SQLitePragmas { + sqliteConfig.ConnectAttributes["_"+k] = v + } + + if cfg.FrontendPort == 0 { + cfg.FrontendPort = provider.MustGetFreePort() + } + if cfg.MetricsPort == 0 { + cfg.MetricsPort = provider.MustGetFreePort() + } + pprofPort := provider.MustGetFreePort() + + serverConfig.Global.Membership = config.Membership{ + MaxJoinDuration: 30 * time.Second, + BroadcastAddress: localBroadcastAddress, + } + serverConfig.Global.Metrics = &metrics.Config{ + Prometheus: &metrics.PrometheusConfig{ + ListenAddress: fmt.Sprintf("%s:%d", cfg.FrontendIP, cfg.MetricsPort), + HandlerPath: "/metrics", + }, + } + serverConfig.Global.PProf = config.PProf{Port: pprofPort} + serverConfig.Persistence = config.Persistence{ + DefaultStore: sqliteplugin.PluginName, + VisibilityStore: sqliteplugin.PluginName, + NumHistoryShards: 1, + DataStores: map[string]config.DataStore{ + sqliteplugin.PluginName: {SQL: &sqliteConfig}, + }, + } + serverConfig.ClusterMetadata = &cluster.Config{ + EnableGlobalNamespace: false, + FailoverVersionIncrement: 10, + MasterClusterName: "active", + CurrentClusterName: "active", + ClusterInformation: map[string]cluster.ClusterInformation{ + "active": { + Enabled: true, + InitialFailoverVersion: 1, + RPCAddress: fmt.Sprintf("%s:%d", localBroadcastAddress, cfg.FrontendPort), + }, + }, + } + serverConfig.DCRedirectionPolicy = config.DCRedirectionPolicy{ + Policy: "noop", + } + serverConfig.Services = map[string]config.Service{ + "frontend": cfg.mustGetService(0, provider), + "history": cfg.mustGetService(1, provider), + "matching": cfg.mustGetService(2, provider), + "worker": cfg.mustGetService(3, provider), + } + serverConfig.Archival = config.Archival{ + History: config.HistoryArchival{ + State: "disabled", + EnableRead: false, + Provider: nil, + }, + Visibility: config.VisibilityArchival{ + State: "disabled", + EnableRead: false, + Provider: nil, + }, + } + serverConfig.PublicClient = config.PublicClient{ + HostPort: fmt.Sprintf("%s:%d", localBroadcastAddress, cfg.FrontendPort), + } + serverConfig.NamespaceDefaults = config.NamespaceDefaults{ + Archival: config.ArchivalNamespaceDefaults{ + History: config.HistoryArchivalNamespaceDefaults{ + State: "disabled", + }, + Visibility: config.VisibilityArchivalNamespaceDefaults{ + State: "disabled", + }, + }, + } +} + +func (cfg *LiteServerConfig) applyDefaults() { + if cfg.BaseConfig == nil { + cfg.BaseConfig = &config.Config{} + } + if cfg.Logger == nil { + cfg.Logger = log.NewZapLogger(log.BuildZapLogger(log.Config{ + Stdout: true, + Level: "info", + OutputFile: "", + })) + } +} + +func (cfg *LiteServerConfig) validate() error { + for pragma := range cfg.SQLitePragmas { + if _, ok := supportedPragmas[strings.ToLower(pragma)]; !ok { + return fmt.Errorf("unsupported sqlite pragma %q. allowed pragmas: %v", pragma, getAllowedPragmas()) + } + } + + if cfg.Ephemeral && cfg.DatabaseFilePath != "" { + return fmt.Errorf("config option DatabaseFilePath is not supported in ephemeral mode") + } + if !cfg.Ephemeral && cfg.DatabaseFilePath == "" { + return fmt.Errorf("config option DatabaseFilePath is required when ephemeral mode disabled") + } + + return nil +} + +// LiteServer is a high level wrapper for temporal.LiteServer that automatically configures a sqlite backend. +type LiteServer struct { + internal Server + frontendHostPort string +} + +// NewLiteServer returns a Server with a sqlite backend. +func NewLiteServer(liteConfig *LiteServerConfig, opts ...ServerOption) (*LiteServer, error) { + liteConfig.applyDefaults() + if err := liteConfig.validate(); err != nil { + return nil, err + } + + p := newPortProvider() + liteConfig.apply(liteConfig.BaseConfig, p) + if err := p.Close(); err != nil { + return nil, err + } + + sqlConfig := liteConfig.BaseConfig.Persistence.DataStores[sqliteplugin.PluginName].SQL + + if !liteConfig.Ephemeral { + // Apply migrations if file does not already exist + if _, err := os.Stat(liteConfig.DatabaseFilePath); os.IsNotExist(err) { + // Check if any of the parent dirs are missing + dir := filepath.Dir(liteConfig.DatabaseFilePath) + if _, err := os.Stat(dir); err != nil { + return nil, fmt.Errorf("error setting up schema: %w", err) + } + + if err := sqlite.SetupSchema(sqlConfig); err != nil { + return nil, fmt.Errorf("error setting up schema: %w", err) + } + } + } + // Pre-create namespaces + var namespaces []*sqlite.NamespaceConfig + for _, ns := range liteConfig.Namespaces { + namespaces = append(namespaces, sqlite.NewNamespaceConfig(liteConfig.BaseConfig.ClusterMetadata.CurrentClusterName, ns, false)) + } + if err := sqlite.CreateNamespaces(sqlConfig, namespaces...); err != nil { + return nil, fmt.Errorf("error creating namespaces: %w", err) + } + + authorizer, err := authorization.GetAuthorizerFromConfig(&liteConfig.BaseConfig.Global.Authorization) + if err != nil { + return nil, fmt.Errorf("unable to instantiate authorizer: %w", err) + } + + claimMapper, err := authorization.GetClaimMapperFromConfig(&liteConfig.BaseConfig.Global.Authorization, liteConfig.Logger) + if err != nil { + return nil, fmt.Errorf("unable to instantiate claim mapper: %w", err) + } + + serverOpts := []ServerOption{ + WithConfig(liteConfig.BaseConfig), + ForServices(DefaultServices), + WithLogger(liteConfig.Logger), + WithAuthorizer(authorizer), + WithClaimMapper(func(cfg *config.Config) authorization.ClaimMapper { + return claimMapper + }), + } + + if len(liteConfig.DynamicConfig) > 0 { + // To prevent having to code fall-through semantics right now, we currently + // eagerly fail if dynamic config is being configured in two ways + if liteConfig.BaseConfig.DynamicConfigClient != nil { + return nil, fmt.Errorf("unable to have file-based dynamic config and individual dynamic config values") + } + serverOpts = append(serverOpts, WithDynamicConfigClient(liteConfig.DynamicConfig)) + } + + if len(opts) > 0 { + serverOpts = append(serverOpts, opts...) + } + + srv, err := NewServer(serverOpts...) + if err != nil { + return nil, fmt.Errorf("unable to instantiate server: %w", err) + } + + s := &LiteServer{ + internal: srv, + frontendHostPort: liteConfig.BaseConfig.PublicClient.HostPort, + } + + return s, nil +} + +// Start temporal server. +func (s *LiteServer) Start() error { + // We wrap Server instead of simply embedding it in the LiteServer struct so + // that it's possible to add additional lifecycle hooks here if necessary. + return s.internal.Start() +} + +// Stop the server. +func (s *LiteServer) Stop() { + // We wrap Server instead of simply embedding it in the LiteServer struct so + // that it's possible to add additional lifecycle hooks here if necessary. + s.internal.Stop() +} + +// NewClient initializes a client ready to communicate with the Temporal +// server in the target namespace. +func (s *LiteServer) NewClient(ctx context.Context, namespace string) (client.Client, error) { + return s.NewClientWithOptions(ctx, client.Options{Namespace: namespace}) +} + +// NewClientWithOptions is the same as NewClient but allows further customization. +// +// To set the client's namespace, use the corresponding field in client.Options. +// +// Note that the HostPort and ConnectionOptions fields of client.Options will always be overridden. +func (s *LiteServer) NewClientWithOptions(ctx context.Context, options client.Options) (client.Client, error) { + options.HostPort = s.frontendHostPort + return client.NewClient(options) +} + +// FrontendHostPort returns the host:port for this server. +// +// When constructing a Temporalite client from within the same process, +// NewClient or NewClientWithOptions should be used instead. +func (s *LiteServer) FrontendHostPort() string { + return s.frontendHostPort +} + +var supportedPragmas = map[string]struct{}{ + "journal_mode": {}, + "synchronous": {}, +} + +func getAllowedPragmas() []string { + var allowedPragmaList []string + for k := range supportedPragmas { + allowedPragmaList = append(allowedPragmaList, k) + } + sort.Strings(allowedPragmaList) + return allowedPragmaList +} + +func (cfg *LiteServerConfig) mustGetService(frontendPortOffset int, provider *portProvider) config.Service { + svc := config.Service{ + RPC: config.RPC{ + GRPCPort: cfg.FrontendPort + frontendPortOffset, + MembershipPort: cfg.FrontendPort + 100 + frontendPortOffset, + BindOnLocalHost: true, + BindOnIP: "", + }, + } + + // Assign any open port when configured to use dynamic ports + if frontendPortOffset != 0 { + svc.RPC.GRPCPort = provider.MustGetFreePort() + } + svc.RPC.MembershipPort = provider.MustGetFreePort() + + // Optionally bind frontend to IPv4 address + if frontendPortOffset == 0 && cfg.FrontendIP != "" { + svc.RPC.BindOnLocalHost = false + svc.RPC.BindOnIP = cfg.FrontendIP + } + + return svc +} diff --git a/temporal/temporalite/README.md b/temporal/temporalite/README.md deleted file mode 100644 index f3e16e3259d..00000000000 --- a/temporal/temporalite/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Temporalite - -> ⚠️ This package is currently experimental and not suitable for production use. ⚠️ - -Temporalite is a distribution of Temporal that runs as a single process with zero runtime dependencies. - -Persistence to disk and an in-memory mode are both supported via SQLite. - -## Why - -The goal of Temporalite is to make it simple and fast to run single-node Temporal servers for development, testing, and production use cases. - -Features that align with this goal: - -- High level library for configuring a SQLite backed Temporal server -- Fast startup time -- Minimal resource overhead: no dependencies on a container runtime or database server -- Support for Windows, Linux, and macOS - -## Backwards Compatability - -This package must not break backwards compatability in accordance with semantic versioning. diff --git a/temporal/temporalite/config.go b/temporal/temporalite/config.go deleted file mode 100644 index f3826488a68..00000000000 --- a/temporal/temporalite/config.go +++ /dev/null @@ -1,214 +0,0 @@ -// The MIT License -// -// Copyright (c) 2021 Datadog, Inc. -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package temporalite - -import ( - "math/rand" - "os" - "time" - - "fmt" - "path/filepath" - "sort" - - "go.temporal.io/server/common/cluster" - "go.temporal.io/server/common/config" - "go.temporal.io/server/common/log" - "go.temporal.io/server/common/metrics" - "go.temporal.io/server/common/persistence/sql/sqlplugin/sqlite" -) - -const ( - broadcastAddress = "127.0.0.1" - defaultFrontendPort = 7233 -) - -var supportedPragmas = map[string]struct{}{ - "journal_mode": {}, - "synchronous": {}, -} - -func getAllowedPragmas() []string { - var allowedPragmaList []string - for k := range supportedPragmas { - allowedPragmaList = append(allowedPragmaList, k) - } - sort.Strings(allowedPragmaList) - return allowedPragmaList -} - -func newDefaultConfig() (*serverConfig, error) { - userConfigDir, err := os.UserConfigDir() - if err != nil { - return nil, fmt.Errorf("cannot determine user config directory: %w", err) - } - - return &serverConfig{ - Ephemeral: false, - DatabaseFilePath: filepath.Join(userConfigDir, "temporalite", "db", "default.db"), - FrontendPort: 0, - MetricsPort: 0, - Namespaces: nil, - SQLitePragmas: nil, - Logger: log.NewZapLogger(log.BuildZapLogger(log.Config{ - Stdout: true, - Level: "info", - OutputFile: "", - })), - portProvider: newPortProvider(), - FrontendIP: "", - BaseConfig: &config.Config{}, - }, nil -} - -func convertLiteConfig(cfg *serverConfig) *config.Config { - defer func() { - if err := cfg.portProvider.Close(); err != nil { - panic(err) - } - }() - - sqliteConfig := config.SQL{ - PluginName: sqlite.PluginName, - ConnectAttributes: make(map[string]string), - DatabaseName: cfg.DatabaseFilePath, - } - if cfg.Ephemeral { - sqliteConfig.ConnectAttributes["mode"] = "memory" - sqliteConfig.ConnectAttributes["cache"] = "shared" - sqliteConfig.DatabaseName = fmt.Sprintf("%d", rand.Intn(9999999)) - } else { - sqliteConfig.ConnectAttributes["mode"] = "rwc" - } - - for k, v := range cfg.SQLitePragmas { - sqliteConfig.ConnectAttributes["_"+k] = v - } - - if cfg.FrontendPort == 0 { - cfg.FrontendPort = cfg.portProvider.MustGetFreePort() - } - if cfg.MetricsPort == 0 { - cfg.MetricsPort = cfg.portProvider.MustGetFreePort() - } - pprofPort := cfg.portProvider.MustGetFreePort() - - baseConfig := cfg.BaseConfig - baseConfig.Global.Membership = config.Membership{ - MaxJoinDuration: 30 * time.Second, - BroadcastAddress: broadcastAddress, - } - baseConfig.Global.Metrics = &metrics.Config{ - Prometheus: &metrics.PrometheusConfig{ - ListenAddress: fmt.Sprintf("%s:%d", cfg.FrontendIP, cfg.MetricsPort), - HandlerPath: "/metrics", - }, - } - baseConfig.Global.PProf = config.PProf{Port: pprofPort} - baseConfig.Persistence = config.Persistence{ - DefaultStore: sqlite.PluginName, - VisibilityStore: sqlite.PluginName, - NumHistoryShards: 1, - DataStores: map[string]config.DataStore{ - sqlite.PluginName: {SQL: &sqliteConfig}, - }, - } - baseConfig.ClusterMetadata = &cluster.Config{ - EnableGlobalNamespace: false, - FailoverVersionIncrement: 10, - MasterClusterName: "active", - CurrentClusterName: "active", - ClusterInformation: map[string]cluster.ClusterInformation{ - "active": { - Enabled: true, - InitialFailoverVersion: 1, - RPCAddress: fmt.Sprintf("%s:%d", broadcastAddress, cfg.FrontendPort), - }, - }, - } - baseConfig.DCRedirectionPolicy = config.DCRedirectionPolicy{ - Policy: "noop", - } - baseConfig.Services = map[string]config.Service{ - "frontend": cfg.mustGetService(0), - "history": cfg.mustGetService(1), - "matching": cfg.mustGetService(2), - "worker": cfg.mustGetService(3), - } - baseConfig.Archival = config.Archival{ - History: config.HistoryArchival{ - State: "disabled", - EnableRead: false, - Provider: nil, - }, - Visibility: config.VisibilityArchival{ - State: "disabled", - EnableRead: false, - Provider: nil, - }, - } - baseConfig.PublicClient = config.PublicClient{ - HostPort: fmt.Sprintf("%s:%d", broadcastAddress, cfg.FrontendPort), - } - baseConfig.NamespaceDefaults = config.NamespaceDefaults{ - Archival: config.ArchivalNamespaceDefaults{ - History: config.HistoryArchivalNamespaceDefaults{ - State: "disabled", - }, - Visibility: config.VisibilityArchivalNamespaceDefaults{ - State: "disabled", - }, - }, - } - - return baseConfig -} - -func (cfg *serverConfig) mustGetService(frontendPortOffset int) config.Service { - svc := config.Service{ - RPC: config.RPC{ - GRPCPort: cfg.FrontendPort + frontendPortOffset, - MembershipPort: cfg.FrontendPort + 100 + frontendPortOffset, - BindOnLocalHost: true, - BindOnIP: "", - }, - } - - // Assign any open port when configured to use dynamic ports - if frontendPortOffset != 0 { - svc.RPC.GRPCPort = cfg.portProvider.MustGetFreePort() - } - svc.RPC.MembershipPort = cfg.portProvider.MustGetFreePort() - - // Optionally bind frontend to IPv4 address - if frontendPortOffset == 0 && cfg.FrontendIP != "" { - svc.RPC.BindOnLocalHost = false - svc.RPC.BindOnIP = cfg.FrontendIP - } - - return svc -} diff --git a/temporal/temporalite/options.go b/temporal/temporalite/options.go deleted file mode 100644 index 895558b763c..00000000000 --- a/temporal/temporalite/options.go +++ /dev/null @@ -1,189 +0,0 @@ -// The MIT License -// -// Copyright (c) 2021 Datadog, Inc. -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package temporalite - -import ( - "go.temporal.io/server/common/config" - "go.temporal.io/server/common/dynamicconfig" - "go.temporal.io/server/common/log" - "go.temporal.io/server/temporal" -) - -type serverConfig struct { - // When true, Ephemeral disables file persistence and uses the in-memory storage driver. - // State will be reset on each process restart. - Ephemeral bool - // DatabaseFilePath persists state to the file at the specified path. - // - // This is required if Ephemeral is false. - DatabaseFilePath string - FrontendPort int - // WithMetricsPort sets the listening port for metrics. - // - // When unspecified, the port will be system-chosen. - MetricsPort int - // Namespaces specified here will be automatically registered on Temporal start. - Namespaces []string - // SQLitePragmas specified here will be applied as pragma statements to SQLite on Temporal start. - SQLitePragmas map[string]string - // Logger overrides the default logger. - Logger log.Logger - // ServerOptions to be applied on Temporal start. - ServerOptions []temporal.ServerOption - portProvider *portProvider - FrontendIP string - // BaseConfig sets the default Temporal server configuration. - // - // Storage and client configuration will always be overridden, however base config can be - // used to enable settings like TLS or authentication. - BaseConfig *config.Config - // DynamicConfig sets dynamic config values used by the server. - DynamicConfig dynamicconfig.StaticClient -} - -// WithLogger overrides the default logger. -func WithLogger(logger log.Logger) ServerOption { - return newApplyFuncContainer(func(cfg *serverConfig) { - cfg.Logger = logger - }) -} - -// WithDatabaseFilePath persists state to the file at the specified path. -func WithDatabaseFilePath(filepath string) ServerOption { - return newApplyFuncContainer(func(cfg *serverConfig) { - cfg.Ephemeral = false - cfg.DatabaseFilePath = filepath - }) -} - -// WithPersistenceDisabled disables file persistence and uses the in-memory storage driver. -// State will be reset on each process restart. -func WithPersistenceDisabled() ServerOption { - return newApplyFuncContainer(func(cfg *serverConfig) { - cfg.Ephemeral = true - }) -} - -// WithFrontendPort sets the listening port for the temporal-frontend GRPC service. -// -// When unspecified, the default port number of 7233 is used. -func WithFrontendPort(port int) ServerOption { - return newApplyFuncContainer(func(cfg *serverConfig) { - cfg.FrontendPort = port - }) -} - -// WithMetricsPort sets the listening port for metrics. -// -// When unspecified, the port will be system-chosen. -func WithMetricsPort(port int) ServerOption { - return newApplyFuncContainer(func(cfg *serverConfig) { - cfg.MetricsPort = port - }) -} - -// WithFrontendIP binds the temporal-frontend GRPC service to a specific IP (eg. `0.0.0.0`) -// Check net.ParseIP for supported syntax; only IPv4 is supported. -// -// When unspecified, the frontend service will bind to localhost. -func WithFrontendIP(address string) ServerOption { - return newApplyFuncContainer(func(cfg *serverConfig) { - cfg.FrontendIP = address - }) -} - -// WithNamespaces registers each namespace on Temporal start. -func WithNamespaces(namespaces ...string) ServerOption { - return newApplyFuncContainer(func(cfg *serverConfig) { - cfg.Namespaces = append(cfg.Namespaces, namespaces...) - }) -} - -// WithSQLitePragmas applies pragma statements to SQLite on Temporal start. -func WithSQLitePragmas(pragmas map[string]string) ServerOption { - return newApplyFuncContainer(func(cfg *serverConfig) { - if cfg.SQLitePragmas == nil { - cfg.SQLitePragmas = make(map[string]string) - } - for k, v := range pragmas { - cfg.SQLitePragmas[k] = v - } - }) -} - -// WithOptions registers Temporal server options. -func WithOptions(options ...temporal.ServerOption) ServerOption { - return newApplyFuncContainer(func(cfg *serverConfig) { - cfg.ServerOptions = append(cfg.ServerOptions, options...) - }) -} - -// WithBaseConfig sets the default Temporal server configuration. -// -// Storage and client configuration will always be overridden, however base config can be -// used to enable settings like TLS or authentication. -func WithBaseConfig(base *config.Config) ServerOption { - return newApplyFuncContainer(func(cfg *serverConfig) { - cfg.BaseConfig = base - }) -} - -// WithDynamicConfigValue sets the given dynamic config key with the given set -// of values. This will overwrite a key if already set. -func WithDynamicConfigValue(key dynamicconfig.Key, value []dynamicconfig.ConstrainedValue) ServerOption { - return newApplyFuncContainer(func(cfg *serverConfig) { - if cfg.DynamicConfig == nil { - cfg.DynamicConfig = dynamicconfig.StaticClient{} - } - cfg.DynamicConfig[key] = value - }) -} - -// WithSearchAttributeCacheDisabled forces refreshing the search attributes cache during each read operation. -// -// This effectively bypasses any cached value and is used to facilitate testing of changes in search attributes. -// This should not be turned on in production. -func WithSearchAttributeCacheDisabled() ServerOption { - return WithDynamicConfigValue( - dynamicconfig.ForceSearchAttributesCacheRefreshOnRead, - []dynamicconfig.ConstrainedValue{{Value: true}}, - ) -} - -type applyFuncContainer struct { - applyInternal func(*serverConfig) -} - -func (fso *applyFuncContainer) apply(cfg *serverConfig) { - fso.applyInternal(cfg) -} - -func newApplyFuncContainer(apply func(*serverConfig)) *applyFuncContainer { - return &applyFuncContainer{ - applyInternal: apply, - } -} diff --git a/temporal/temporalite/server.go b/temporal/temporalite/server.go deleted file mode 100644 index 1657ebcf111..00000000000 --- a/temporal/temporalite/server.go +++ /dev/null @@ -1,181 +0,0 @@ -// The MIT License -// -// Copyright (c) 2021 Datadog, Inc. -// -// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. -// -// Copyright (c) 2020 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package temporalite - -import ( - "os" - - "context" - "fmt" - "path/filepath" - "strings" - - "go.temporal.io/sdk/client" - - "go.temporal.io/server/common/authorization" - "go.temporal.io/server/common/config" - sqliteplugin "go.temporal.io/server/common/persistence/sql/sqlplugin/sqlite" - "go.temporal.io/server/schema/sqlite" - "go.temporal.io/server/temporal" -) - -// Server is a high level wrapper for temporal.Server that automatically configures a sqlite backend. -type Server struct { - internal temporal.Server - frontendHostPort string - config *serverConfig -} - -type ServerOption interface { - apply(*serverConfig) -} - -// NewServer returns a Server with a sqlite backend. -func NewServer(opts ...ServerOption) (*Server, error) { - c, err := newDefaultConfig() - if err != nil { - return nil, err - } - for _, opt := range opts { - opt.apply(c) - } - - for pragma := range c.SQLitePragmas { - if _, ok := supportedPragmas[strings.ToLower(pragma)]; !ok { - return nil, fmt.Errorf("ERROR: unsupported pragma %q, %v allowed", pragma, getAllowedPragmas()) - } - } - - cfg := convertLiteConfig(c) - sqlConfig := cfg.Persistence.DataStores[sqliteplugin.PluginName].SQL - - if !c.Ephemeral { - // Apply migrations if file does not already exist - if _, err := os.Stat(c.DatabaseFilePath); os.IsNotExist(err) { - // Check if any of the parent dirs are missing - dir := filepath.Dir(c.DatabaseFilePath) - if _, err := os.Stat(dir); err != nil { - return nil, fmt.Errorf("error setting up schema: %w", err) - } - - if err := sqlite.SetupSchema(sqlConfig); err != nil { - return nil, fmt.Errorf("error setting up schema: %w", err) - } - } - } - // Pre-create namespaces - var namespaces []*sqlite.NamespaceConfig - for _, ns := range c.Namespaces { - namespaces = append(namespaces, sqlite.NewNamespaceConfig(cfg.ClusterMetadata.CurrentClusterName, ns, false)) - } - if err := sqlite.CreateNamespaces(sqlConfig, namespaces...); err != nil { - return nil, fmt.Errorf("error creating namespaces: %w", err) - } - - authorizer, err := authorization.GetAuthorizerFromConfig(&cfg.Global.Authorization) - if err != nil { - return nil, fmt.Errorf("unable to instantiate authorizer: %w", err) - } - - claimMapper, err := authorization.GetClaimMapperFromConfig(&cfg.Global.Authorization, c.Logger) - if err != nil { - return nil, fmt.Errorf("unable to instantiate claim mapper: %w", err) - } - - serverOpts := []temporal.ServerOption{ - temporal.WithConfig(cfg), - temporal.ForServices(temporal.DefaultServices), - temporal.WithLogger(c.Logger), - temporal.WithAuthorizer(authorizer), - temporal.WithClaimMapper(func(cfg *config.Config) authorization.ClaimMapper { - return claimMapper - }), - } - - if len(c.DynamicConfig) > 0 { - // To prevent having to code fall-through semantics right now, we currently - // eagerly fail if dynamic config is being configured in two ways - if cfg.DynamicConfigClient != nil { - return nil, fmt.Errorf("unable to have file-based dynamic config and individual dynamic config values") - } - serverOpts = append(serverOpts, temporal.WithDynamicConfigClient(c.DynamicConfig)) - } - - if len(c.ServerOptions) > 0 { - serverOpts = append(serverOpts, c.ServerOptions...) - } - - srv, err := temporal.NewServer(serverOpts...) - if err != nil { - return nil, fmt.Errorf("unable to instantiate server: %w", err) - } - - s := &Server{ - internal: srv, - frontendHostPort: cfg.PublicClient.HostPort, - config: c, - } - - return s, nil -} - -// Start temporal server. -func (s *Server) Start() error { - return s.internal.Start() -} - -// Stop the server. -func (s *Server) Stop() { - if s == nil { - return - } - s.internal.Stop() -} - -// NewClient initializes a client ready to communicate with the Temporal -// server in the target namespace. -func (s *Server) NewClient(ctx context.Context, namespace string) (client.Client, error) { - return s.NewClientWithOptions(ctx, client.Options{Namespace: namespace}) -} - -// NewClientWithOptions is the same as NewClient but allows further customization. -// -// To set the client's namespace, use the corresponding field in client.Options. -// -// Note that the HostPort and ConnectionOptions fields of client.Options will always be overridden. -func (s *Server) NewClientWithOptions(ctx context.Context, options client.Options) (client.Client, error) { - options.HostPort = s.frontendHostPort - return client.NewClient(options) -} - -// FrontendHostPort returns the host:port for this server. -// -// When constructing a Temporalite client from within the same process, -// NewClient or NewClientWithOptions should be used instead. -func (s *Server) FrontendHostPort() string { - return s.frontendHostPort -} diff --git a/temporal/temporaltest/options.go b/temporal/temporaltest/options.go index 0cd28a7af2f..3a6577c7fb1 100644 --- a/temporal/temporaltest/options.go +++ b/temporal/temporaltest/options.go @@ -31,8 +31,7 @@ import ( "go.temporal.io/sdk/client" "go.temporal.io/sdk/worker" - - "go.temporal.io/server/temporal/temporalite" + "go.temporal.io/server/temporal" ) type TestServerOption interface { @@ -67,8 +66,8 @@ func WithBaseWorkerOptions(o worker.Options) TestServerOption { }) } -// WithTemporaliteOptions provides the ability to use additional Temporalite options, including temporalite.WithUpstreamOptions. -func WithTemporaliteOptions(options ...temporalite.ServerOption) TestServerOption { +// WithBaseServerOptions enables configuring additional server options not directly exposed via temporaltest. +func WithBaseServerOptions(options ...temporal.ServerOption) TestServerOption { return newApplyFuncContainer(func(server *TestServer) { server.serverOptions = append(server.serverOptions, options...) }) diff --git a/temporal/temporaltest/server.go b/temporal/temporaltest/server.go index 7e4c821a36d..ab6d25631a6 100644 --- a/temporal/temporaltest/server.go +++ b/temporal/temporaltest/server.go @@ -38,15 +38,15 @@ import ( "go.temporal.io/sdk/client" "go.temporal.io/sdk/worker" + "go.temporal.io/server/common/dynamicconfig" "go.temporal.io/server/common/log" - - "go.temporal.io/server/temporal/temporalite" + "go.temporal.io/server/temporal" ) // A TestServer is a Temporal server listening on a system-chosen port on the // local loopback interface, for use in end-to-end tests. type TestServer struct { - server *temporalite.Server + server *temporal.LiteServer defaultTestNamespace string defaultClient client.Client clients []client.Client @@ -54,7 +54,7 @@ type TestServer struct { t *testing.T defaultClientOptions client.Options defaultWorkerOptions worker.Options - serverOptions []temporalite.ServerOption + serverOptions []temporal.ServerOption } func (ts *TestServer) fatal(err error) { @@ -169,18 +169,16 @@ func NewServer(opts ...TestServerOption) *TestServer { }) } - // Order of these options matters. When there are conflicts, options later in the list take precedence. - // Always specify options that are required for temporaltest last to avoid accidental overrides. - ts.serverOptions = append(ts.serverOptions, - temporalite.WithNamespaces(ts.defaultTestNamespace), - temporalite.WithPersistenceDisabled(), - temporalite.WithLogger(log.NewNoopLogger()), - temporalite.WithSearchAttributeCacheDisabled(), + s, err := temporal.NewLiteServer(&temporal.LiteServerConfig{ + Namespaces: []string{ts.defaultTestNamespace}, + Ephemeral: true, + Logger: log.NewNoopLogger(), + DynamicConfig: dynamicconfig.StaticClient{ + dynamicconfig.ForceSearchAttributesCacheRefreshOnRead: []dynamicconfig.ConstrainedValue{{Value: true}}, + }, // Disable "accept incoming network connections?" prompt on macOS - temporalite.WithFrontendIP("127.0.0.1"), - ) - - s, err := temporalite.NewServer(ts.serverOptions...) + FrontendIP: "127.0.0.1", + }, ts.serverOptions...) if err != nil { ts.fatal(fmt.Errorf("error creating server: %w", err)) } From 102288805ea76102b4973df7f1b61f25ef50a6b5 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 21 Mar 2023 22:03:48 -0400 Subject: [PATCH 11/48] Remove PublicClient config --- temporal/lite_server.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/temporal/lite_server.go b/temporal/lite_server.go index 4a94c2239e1..db7bb71cbdd 100644 --- a/temporal/lite_server.go +++ b/temporal/lite_server.go @@ -133,9 +133,6 @@ func (cfg *LiteServerConfig) apply(serverConfig *config.Config, provider *portPr Provider: nil, }, } - serverConfig.PublicClient = config.PublicClient{ - HostPort: fmt.Sprintf("%s:%d", localBroadcastAddress, cfg.FrontendPort), - } serverConfig.NamespaceDefaults = config.NamespaceDefaults{ Archival: config.ArchivalNamespaceDefaults{ History: config.HistoryArchivalNamespaceDefaults{ @@ -262,7 +259,7 @@ func NewLiteServer(liteConfig *LiteServerConfig, opts ...ServerOption) (*LiteSer s := &LiteServer{ internal: srv, - frontendHostPort: liteConfig.BaseConfig.PublicClient.HostPort, + frontendHostPort: fmt.Sprintf("%s:%d", liteConfig.FrontendIP, liteConfig.FrontendPort), } return s, nil From e650816382f7174f1d90f5a7b4076c0200c55aff Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 21 Mar 2023 22:05:43 -0400 Subject: [PATCH 12/48] Revert "Remove PublicClient config" This reverts commit c56ea227139ae979974ba74b7522862abd05aa1a. --- temporal/lite_server.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/temporal/lite_server.go b/temporal/lite_server.go index db7bb71cbdd..4a94c2239e1 100644 --- a/temporal/lite_server.go +++ b/temporal/lite_server.go @@ -133,6 +133,9 @@ func (cfg *LiteServerConfig) apply(serverConfig *config.Config, provider *portPr Provider: nil, }, } + serverConfig.PublicClient = config.PublicClient{ + HostPort: fmt.Sprintf("%s:%d", localBroadcastAddress, cfg.FrontendPort), + } serverConfig.NamespaceDefaults = config.NamespaceDefaults{ Archival: config.ArchivalNamespaceDefaults{ History: config.HistoryArchivalNamespaceDefaults{ @@ -259,7 +262,7 @@ func NewLiteServer(liteConfig *LiteServerConfig, opts ...ServerOption) (*LiteSer s := &LiteServer{ internal: srv, - frontendHostPort: fmt.Sprintf("%s:%d", liteConfig.FrontendIP, liteConfig.FrontendPort), + frontendHostPort: liteConfig.BaseConfig.PublicClient.HostPort, } return s, nil From 34760eb1bc8071d213eb757099d7f75ce7ed09ce Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 21 Mar 2023 22:58:26 -0400 Subject: [PATCH 13/48] Add license header --- temporal/lite_server.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/temporal/lite_server.go b/temporal/lite_server.go index 4a94c2239e1..78285cfbda8 100644 --- a/temporal/lite_server.go +++ b/temporal/lite_server.go @@ -1,3 +1,29 @@ +// The MIT License +// +// Copyright (c) 2021 Datadog, Inc. +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + package temporal import ( From 9faaebc17a3608a6a43eb6528bb1bdcc1cc12544 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 21 Mar 2023 23:15:41 -0400 Subject: [PATCH 14/48] Test setting BaseServerOptions --- temporal/temporaltest/server_test.go | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/temporal/temporaltest/server_test.go b/temporal/temporaltest/server_test.go index 811ea400430..98279e3e145 100644 --- a/temporal/temporaltest/server_test.go +++ b/temporal/temporaltest/server_test.go @@ -27,6 +27,7 @@ package temporaltest_test import ( + "strings" "testing" "time" @@ -41,6 +42,9 @@ import ( "go.temporal.io/sdk/worker" "go.temporal.io/sdk/workflow" + "go.temporal.io/server/common/authorization" + "go.temporal.io/server/common/config" + "go.temporal.io/server/temporal" "go.temporal.io/server/temporal/temporaltest" ) @@ -184,6 +188,39 @@ func TestDefaultWorkerOptions(t *testing.T) { } } +type denyAllClaimMapper struct{} + +func (denyAllClaimMapper) GetClaims(*authorization.AuthInfo) (*authorization.Claims, error) { + return nil, fmt.Errorf("no claims for you!") +} + +func TestBaseServerOptions(t *testing.T) { + // This test verifies that we can set custom claim mappers and authorizers + // with BaseServerOptions. + ts := temporaltest.NewServer( + temporaltest.WithT(t), + temporaltest.WithBaseServerOptions( + temporal.WithClaimMapper(func(cfg *config.Config) authorization.ClaimMapper { + return denyAllClaimMapper{} + }), + temporal.WithAuthorizer(authorization.NewDefaultAuthorizer()), + ), + ) + + _, err := ts.GetDefaultClient().ExecuteWorkflow( + context.Background(), + client.StartWorkflowOptions{}, + "test-workflow", + ) + if err == nil { + t.Fatal("err must be non-nil") + } + + if !strings.Contains(err.Error(), authorization.RequestUnauthorized) { + t.Errorf("expected error %q, got %q", authorization.RequestUnauthorized, err) + } +} + func TestClientWithCustomInterceptor(t *testing.T) { var opts client.Options opts.Interceptors = append(opts.Interceptors, NewTestInterceptor()) From c613ba85226d5a8cee5c891a0d12acfa6a17d72c Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 21 Mar 2023 23:32:32 -0400 Subject: [PATCH 15/48] Update godoc --- temporal/lite_server.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/temporal/lite_server.go b/temporal/lite_server.go index 78285cfbda8..e8a12b672b0 100644 --- a/temporal/lite_server.go +++ b/temporal/lite_server.go @@ -61,9 +61,11 @@ type LiteServerConfig struct { FrontendIP string // Port on which frontend service should listen. FrontendPort int - // WithMetricsPort sets the listening port for metrics. + // WithMetricsPort sets the listening port for the default Prometheus metrics handler. // // When unspecified, the port will be system-chosen. + // + // This field is ignored when the WithCustomMetricsHandler server option is enabled. MetricsPort int // Namespaces specified here will be automatically registered on Temporal start. Namespaces []string @@ -75,6 +77,8 @@ type LiteServerConfig struct { // // Storage and client configuration will always be overridden, however base config can be // used to enable settings like TLS or authentication. + // + // Note that server options can also be passed to the NewLiteServer function. BaseConfig *config.Config // DynamicConfig sets dynamic config values used by the server. DynamicConfig dynamicconfig.StaticClient @@ -190,7 +194,7 @@ func (cfg *LiteServerConfig) applyDefaults() { func (cfg *LiteServerConfig) validate() error { for pragma := range cfg.SQLitePragmas { if _, ok := supportedPragmas[strings.ToLower(pragma)]; !ok { - return fmt.Errorf("unsupported sqlite pragma %q. allowed pragmas: %v", pragma, getAllowedPragmas()) + return fmt.Errorf("unsupported SQLite pragma %q. allowed pragmas: %v", pragma, getAllowedPragmas()) } } @@ -204,13 +208,15 @@ func (cfg *LiteServerConfig) validate() error { return nil } -// LiteServer is a high level wrapper for temporal.LiteServer that automatically configures a sqlite backend. +// LiteServer is a high level wrapper for temporal.LiteServer that automatically configures a SQLite backend. type LiteServer struct { internal Server frontendHostPort string } -// NewLiteServer returns a Server with a sqlite backend. +// NewLiteServer initializes a Server with a SQLite backend. +// +// If the db file does not already exist, schema migrations are automatically run. func NewLiteServer(liteConfig *LiteServerConfig, opts ...ServerOption) (*LiteServer, error) { liteConfig.applyDefaults() if err := liteConfig.validate(); err != nil { @@ -239,6 +245,7 @@ func NewLiteServer(liteConfig *LiteServerConfig, opts ...ServerOption) (*LiteSer } } } + // Pre-create namespaces var namespaces []*sqlite.NamespaceConfig for _, ns := range liteConfig.Namespaces { From 0cd959afa33cf8e2ed7465fa569dfcfc65d2f335 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 21 Mar 2023 23:39:37 -0400 Subject: [PATCH 16/48] Move temporaltest to top level --- {temporal/temporaltest => temporaltest}/README.md | 0 {temporal/temporaltest => temporaltest}/logger.go | 0 {temporal/temporaltest => temporaltest}/options.go | 0 {temporal/temporaltest => temporaltest}/server.go | 0 {temporal/temporaltest => temporaltest}/server_test.go | 2 +- 5 files changed, 1 insertion(+), 1 deletion(-) rename {temporal/temporaltest => temporaltest}/README.md (100%) rename {temporal/temporaltest => temporaltest}/logger.go (100%) rename {temporal/temporaltest => temporaltest}/options.go (100%) rename {temporal/temporaltest => temporaltest}/server.go (100%) rename {temporal/temporaltest => temporaltest}/server_test.go (99%) diff --git a/temporal/temporaltest/README.md b/temporaltest/README.md similarity index 100% rename from temporal/temporaltest/README.md rename to temporaltest/README.md diff --git a/temporal/temporaltest/logger.go b/temporaltest/logger.go similarity index 100% rename from temporal/temporaltest/logger.go rename to temporaltest/logger.go diff --git a/temporal/temporaltest/options.go b/temporaltest/options.go similarity index 100% rename from temporal/temporaltest/options.go rename to temporaltest/options.go diff --git a/temporal/temporaltest/server.go b/temporaltest/server.go similarity index 100% rename from temporal/temporaltest/server.go rename to temporaltest/server.go diff --git a/temporal/temporaltest/server_test.go b/temporaltest/server_test.go similarity index 99% rename from temporal/temporaltest/server_test.go rename to temporaltest/server_test.go index 98279e3e145..8a5c1472ad6 100644 --- a/temporal/temporaltest/server_test.go +++ b/temporaltest/server_test.go @@ -45,7 +45,7 @@ import ( "go.temporal.io/server/common/authorization" "go.temporal.io/server/common/config" "go.temporal.io/server/temporal" - "go.temporal.io/server/temporal/temporaltest" + "go.temporal.io/server/temporaltest" ) // to be used in example code From ecd00c4542c4891f897ba6c1ab44976053e7daf9 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 21 Mar 2023 23:53:12 -0400 Subject: [PATCH 17/48] Update temporaltest backwards compatability policy --- temporaltest/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/temporaltest/README.md b/temporaltest/README.md index d0e7c06e13b..5108e2f64af 100644 --- a/temporaltest/README.md +++ b/temporaltest/README.md @@ -2,4 +2,6 @@ The `temporaltest` package provides helpers for writing end to end tests against ## Backwards Compatability -This package must not break backwards compatability in accordance with semantic versioning. +This package must not break Go API backwards compatability in accordance with semantic versioning. One exception to this policy is the `WithBaseServerOptions` function, which may have breaking changes in any Temporal server release. + +The base configuration (eg. dynamic config values) and behavior of `TestServer` may also be modified in any release. Such changes should be for the purposes of improving performance or stability for testing scenarios. From 2e4bd131fc922409dee95a21c4d6e5f562efeeca Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Wed, 22 Mar 2023 13:19:47 -0400 Subject: [PATCH 18/48] Spellcheck --- temporaltest/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/temporaltest/README.md b/temporaltest/README.md index 5108e2f64af..b5658bcf5e2 100644 --- a/temporaltest/README.md +++ b/temporaltest/README.md @@ -1,7 +1,7 @@ The `temporaltest` package provides helpers for writing end to end tests against a real Temporal server which can be run via the `go test` command. -## Backwards Compatability +## Backwards Compatibility -This package must not break Go API backwards compatability in accordance with semantic versioning. One exception to this policy is the `WithBaseServerOptions` function, which may have breaking changes in any Temporal server release. +This package must not break Go API backwards compatibility in accordance with semantic versioning. One exception to this policy is the `WithBaseServerOptions` function, which may have breaking changes in any Temporal server release. The base configuration (eg. dynamic config values) and behavior of `TestServer` may also be modified in any release. Such changes should be for the purposes of improving performance or stability for testing scenarios. From 40fd0941f5cf35c7d0b89dc5dca5ac578eee3896 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 28 Mar 2023 15:32:30 -0400 Subject: [PATCH 19/48] Update godoc --- temporal/lite_server.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/temporal/lite_server.go b/temporal/lite_server.go index e8a12b672b0..b67f7bfd713 100644 --- a/temporal/lite_server.go +++ b/temporal/lite_server.go @@ -49,11 +49,13 @@ import ( const localBroadcastAddress = "127.0.0.1" +// LiteServerConfig encodes options for LiteServer instances. type LiteServerConfig struct { // When true, Ephemeral disables file persistence and uses the in-memory storage driver. // State will be reset on each process restart. Ephemeral bool - // DatabaseFilePath persists state to the file at the specified path. + // DatabaseFilePath persists state to the file at the specified path. If the db file does + // not already exist, it is created and schema migrations are automatically run. // // This is required if Ephemeral is false. DatabaseFilePath string @@ -78,7 +80,9 @@ type LiteServerConfig struct { // Storage and client configuration will always be overridden, however base config can be // used to enable settings like TLS or authentication. // - // Note that server options can also be passed to the NewLiteServer function. + // Note that ServerOption arguments can also be passed to the NewLiteServer function. + // Always prefer setting BaseConfig over using WithConfig however, as WithConfig overrides + // all LiteServer specific settings. BaseConfig *config.Config // DynamicConfig sets dynamic config values used by the server. DynamicConfig dynamicconfig.StaticClient @@ -208,7 +212,7 @@ func (cfg *LiteServerConfig) validate() error { return nil } -// LiteServer is a high level wrapper for temporal.LiteServer that automatically configures a SQLite backend. +// LiteServer is a high level wrapper for Server that automatically configures a SQLite backend. type LiteServer struct { internal Server frontendHostPort string @@ -216,7 +220,11 @@ type LiteServer struct { // NewLiteServer initializes a Server with a SQLite backend. // -// If the db file does not already exist, schema migrations are automatically run. +// Additional configuration can be specified either via variadic ServerOption arguments or +// by setting the BaseConfig field in LiteServerConfig. +// +// Always use BaseConfig instead of the WithConfig server option, as WithConfig overrides all +// LiteServer specific settings. func NewLiteServer(liteConfig *LiteServerConfig, opts ...ServerOption) (*LiteServer, error) { liteConfig.applyDefaults() if err := liteConfig.validate(); err != nil { From 73d31ccda3b370bc2825f143173b504756956d52 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 28 Mar 2023 16:49:50 -0400 Subject: [PATCH 20/48] Add temporaltest package to integration tests --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 90457467353..46d10c14e78 100644 --- a/Makefile +++ b/Makefile @@ -76,7 +76,7 @@ FUNCTIONAL_TEST_XDC_ROOT := ./tests/xdc FUNCTIONAL_TEST_NDC_ROOT := ./tests/ndc DB_INTEGRATION_TEST_ROOT := ./common/persistence/tests DB_TOOL_INTEGRATION_TEST_ROOT := ./tools/tests -INTEGRATION_TEST_DIRS := $(DB_INTEGRATION_TEST_ROOT) $(DB_TOOL_INTEGRATION_TEST_ROOT) +INTEGRATION_TEST_DIRS := $(DB_INTEGRATION_TEST_ROOT) $(DB_TOOL_INTEGRATION_TEST_ROOT) ./temporaltest UNIT_TEST_DIRS := $(filter-out $(FUNCTIONAL_TEST_ROOT)% $(FUNCTIONAL_TEST_XDC_ROOT)% $(FUNCTIONAL_TEST_NDC_ROOT)% $(DB_INTEGRATION_TEST_ROOT)% $(DB_TOOL_INTEGRATION_TEST_ROOT)%,$(TEST_DIRS)) # github.com/urfave/cli/v2@v2.4.0 - needs to accept comma in values before unlocking https://github.com/urfave/cli/pull/1241. From c512a54e6fae13da21ea00d5adcd7464227b29bf Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 25 Apr 2023 13:56:13 -0400 Subject: [PATCH 21/48] Move LiteServer into internal package --- .../temporalite}/freeport.go | 2 +- .../temporalite}/lite_server.go | 27 +++++++++++-------- temporaltest/server.go | 10 +++---- 3 files changed, 22 insertions(+), 17 deletions(-) rename {temporal => internal/temporalite}/freeport.go (99%) rename {temporal => internal/temporalite}/lite_server.go (93%) diff --git a/temporal/freeport.go b/internal/temporalite/freeport.go similarity index 99% rename from temporal/freeport.go rename to internal/temporalite/freeport.go index e02a8384586..6f40bc97f0a 100644 --- a/temporal/freeport.go +++ b/internal/temporalite/freeport.go @@ -24,7 +24,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package temporal +package temporalite import ( "fmt" diff --git a/temporal/lite_server.go b/internal/temporalite/lite_server.go similarity index 93% rename from temporal/lite_server.go rename to internal/temporalite/lite_server.go index b67f7bfd713..07fcfe29ffc 100644 --- a/temporal/lite_server.go +++ b/internal/temporalite/lite_server.go @@ -24,7 +24,10 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -package temporal +// Package temporalite contains high level helpers for setting up a SQLite based server. +package temporalite + +// TODO(jlegrone): Refactor this package into one or more temporal.ServerOption types. import ( "context" @@ -37,6 +40,7 @@ import ( "time" "go.temporal.io/sdk/client" + "go.temporal.io/server/common/authorization" "go.temporal.io/server/common/cluster" "go.temporal.io/server/common/config" @@ -45,6 +49,7 @@ import ( "go.temporal.io/server/common/metrics" sqliteplugin "go.temporal.io/server/common/persistence/sql/sqlplugin/sqlite" "go.temporal.io/server/schema/sqlite" + "go.temporal.io/server/temporal" ) const localBroadcastAddress = "127.0.0.1" @@ -214,7 +219,7 @@ func (cfg *LiteServerConfig) validate() error { // LiteServer is a high level wrapper for Server that automatically configures a SQLite backend. type LiteServer struct { - internal Server + internal temporal.Server frontendHostPort string } @@ -225,7 +230,7 @@ type LiteServer struct { // // Always use BaseConfig instead of the WithConfig server option, as WithConfig overrides all // LiteServer specific settings. -func NewLiteServer(liteConfig *LiteServerConfig, opts ...ServerOption) (*LiteServer, error) { +func NewLiteServer(liteConfig *LiteServerConfig, opts ...temporal.ServerOption) (*LiteServer, error) { liteConfig.applyDefaults() if err := liteConfig.validate(); err != nil { return nil, err @@ -273,12 +278,12 @@ func NewLiteServer(liteConfig *LiteServerConfig, opts ...ServerOption) (*LiteSer return nil, fmt.Errorf("unable to instantiate claim mapper: %w", err) } - serverOpts := []ServerOption{ - WithConfig(liteConfig.BaseConfig), - ForServices(DefaultServices), - WithLogger(liteConfig.Logger), - WithAuthorizer(authorizer), - WithClaimMapper(func(cfg *config.Config) authorization.ClaimMapper { + serverOpts := []temporal.ServerOption{ + temporal.WithConfig(liteConfig.BaseConfig), + temporal.ForServices(temporal.DefaultServices), + temporal.WithLogger(liteConfig.Logger), + temporal.WithAuthorizer(authorizer), + temporal.WithClaimMapper(func(cfg *config.Config) authorization.ClaimMapper { return claimMapper }), } @@ -289,14 +294,14 @@ func NewLiteServer(liteConfig *LiteServerConfig, opts ...ServerOption) (*LiteSer if liteConfig.BaseConfig.DynamicConfigClient != nil { return nil, fmt.Errorf("unable to have file-based dynamic config and individual dynamic config values") } - serverOpts = append(serverOpts, WithDynamicConfigClient(liteConfig.DynamicConfig)) + serverOpts = append(serverOpts, temporal.WithDynamicConfigClient(liteConfig.DynamicConfig)) } if len(opts) > 0 { serverOpts = append(serverOpts, opts...) } - srv, err := NewServer(serverOpts...) + srv, err := temporal.NewServer(serverOpts...) if err != nil { return nil, fmt.Errorf("unable to instantiate server: %w", err) } diff --git a/temporaltest/server.go b/temporaltest/server.go index ab6d25631a6..81682483249 100644 --- a/temporaltest/server.go +++ b/temporaltest/server.go @@ -28,25 +28,25 @@ package temporaltest import ( + "context" + "fmt" "math/rand" "testing" "time" - "context" - "fmt" - "go.temporal.io/sdk/client" "go.temporal.io/sdk/worker" "go.temporal.io/server/common/dynamicconfig" "go.temporal.io/server/common/log" + "go.temporal.io/server/internal/temporalite" "go.temporal.io/server/temporal" ) // A TestServer is a Temporal server listening on a system-chosen port on the // local loopback interface, for use in end-to-end tests. type TestServer struct { - server *temporal.LiteServer + server *temporalite.LiteServer defaultTestNamespace string defaultClient client.Client clients []client.Client @@ -169,7 +169,7 @@ func NewServer(opts ...TestServerOption) *TestServer { }) } - s, err := temporal.NewLiteServer(&temporal.LiteServerConfig{ + s, err := temporalite.NewLiteServer(&temporalite.LiteServerConfig{ Namespaces: []string{ts.defaultTestNamespace}, Ephemeral: true, Logger: log.NewNoopLogger(), From 87a65ed1880caa53f276a344ee6e5d706d29d073 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 25 Apr 2023 17:08:00 -0400 Subject: [PATCH 22/48] Add TODO to remove PublicClient config --- internal/temporalite/lite_server.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/temporalite/lite_server.go b/internal/temporalite/lite_server.go index 07fcfe29ffc..0adfa6db57f 100644 --- a/internal/temporalite/lite_server.go +++ b/internal/temporalite/lite_server.go @@ -172,6 +172,9 @@ func (cfg *LiteServerConfig) apply(serverConfig *config.Config, provider *portPr Provider: nil, }, } + // TODO(dnr): Figure out why server fails to start when PublicClient is not set with error: + // panic: Client must be created with client.Dial() or client.NewLazyClient() + // See also: https://github.com/temporalio/temporal/pull/4026#discussion_r1149808018 serverConfig.PublicClient = config.PublicClient{ HostPort: fmt.Sprintf("%s:%d", localBroadcastAddress, cfg.FrontendPort), } From 43810ea82b6c9bee175087737cefb76ba39b5e1a Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 25 Apr 2023 17:12:53 -0400 Subject: [PATCH 23/48] Document that TestServer methods are unsafe for concurrent use --- temporaltest/server.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/temporaltest/server.go b/temporaltest/server.go index 81682483249..70b3f87ec5d 100644 --- a/temporaltest/server.go +++ b/temporaltest/server.go @@ -45,6 +45,8 @@ import ( // A TestServer is a Temporal server listening on a system-chosen port on the // local loopback interface, for use in end-to-end tests. +// +// Methods on TestServer are not safe for concurrent use. type TestServer struct { server *temporalite.LiteServer defaultTestNamespace string From 6aa60b09b6a5ec1636c1cdb2a8038c52dd45f3cf Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 25 Apr 2023 17:16:50 -0400 Subject: [PATCH 24/48] Don't return an error from denyAllClaimMapper --- temporaltest/server_test.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/temporaltest/server_test.go b/temporaltest/server_test.go index 8a5c1472ad6..7e3d840ddb2 100644 --- a/temporaltest/server_test.go +++ b/temporaltest/server_test.go @@ -27,13 +27,12 @@ package temporaltest_test import ( + "context" + "fmt" "strings" "testing" "time" - "context" - "fmt" - "go.temporal.io/api/enums/v1" "go.temporal.io/api/operatorservice/v1" "go.temporal.io/sdk/activity" @@ -191,7 +190,13 @@ func TestDefaultWorkerOptions(t *testing.T) { type denyAllClaimMapper struct{} func (denyAllClaimMapper) GetClaims(*authorization.AuthInfo) (*authorization.Claims, error) { - return nil, fmt.Errorf("no claims for you!") + // Return claims that have no permissions within the cluster. + return &authorization.Claims{ + Subject: "test-identity", + System: authorization.RoleUndefined, + Namespaces: nil, + Extensions: nil, + }, nil } func TestBaseServerOptions(t *testing.T) { From 3030a0c04dc25933a5d38329d9c3b66fd840586d Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Wed, 26 Jul 2023 15:35:47 -0400 Subject: [PATCH 25/48] Test registering and listing workflows using custom search attribute --- temporaltest/server_test.go | 75 +++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/temporaltest/server_test.go b/temporaltest/server_test.go index 7e3d840ddb2..487557eef7d 100644 --- a/temporaltest/server_test.go +++ b/temporaltest/server_test.go @@ -33,10 +33,13 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "go.temporal.io/api/enums/v1" "go.temporal.io/api/operatorservice/v1" + "go.temporal.io/api/workflowservice/v1" "go.temporal.io/sdk/activity" "go.temporal.io/sdk/client" + "go.temporal.io/sdk/converter" "go.temporal.io/sdk/interceptor" "go.temporal.io/sdk/worker" "go.temporal.io/sdk/workflow" @@ -264,38 +267,74 @@ func TestClientWithCustomInterceptor(t *testing.T) { } } -func TestSearchAttributeCacheDisabled(t *testing.T) { - // TODO(jlegrone) re-enable this test when advanced visibility is enabled in temporalite. - t.Skip("This test case does not currently pass as of the 1.20 release") - +func TestSearchAttributeRegistration(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() ts := temporaltest.NewServer(temporaltest.WithT(t)) + c := ts.GetDefaultClient() - testSearchAttr := "my-search-attr" + testSearchAttr := "MySearchAttr" // Create a search attribute - _, err := ts.GetDefaultClient().OperatorService().AddSearchAttributes(ctx, &operatorservice.AddSearchAttributesRequest{ + if _, err := ts.GetDefaultClient().OperatorService().AddSearchAttributes(ctx, &operatorservice.AddSearchAttributesRequest{ SearchAttributes: map[string]enums.IndexedValueType{ testSearchAttr: enums.INDEXED_VALUE_TYPE_KEYWORD, }, Namespace: ts.GetDefaultNamespace(), + }); err != nil { + t.Fatal(err) + } + // Confirm search attribute is registered immediately + // TODO(jlegrone): investigate why custom search attribute missing here while setting it from workflow succeeds. + //resp, err := c.GetSearchAttributes(ctx) + //if err != nil { + // t.Fatal(err) + //} + //saType, ok := resp.GetKeys()[testSearchAttr] + //if !ok { + // t.Fatalf("search attribute %q is missing from %v", testSearchAttr, resp.GetKeys()) + //} + //if saType != enums.INDEXED_VALUE_TYPE_KEYWORD { + // t.Error("search attribute type does not match expected") + //} + + // Run a workflow that sets the custom search attribute + ts.NewWorker("test", func(registry worker.Registry) { + registry.RegisterWorkflow(SearchAttrWorkflow) }) + wfr, err := c.ExecuteWorkflow(ctx, client.StartWorkflowOptions{ + ID: "search-attr-test", + TaskQueue: "test", + WorkflowExecutionTimeout: 10 * time.Second, + }, SearchAttrWorkflow, testSearchAttr) if err != nil { t.Fatal(err) } - - // Confirm it exists immediately - resp, err := ts.GetDefaultClient().GetSearchAttributes(ctx) - if err != nil { + // Wait for workflow to complete + if err := wfr.Get(ctx, nil); err != nil { t.Fatal(err) } - saType, ok := resp.GetKeys()[testSearchAttr] - if !ok { - t.Fatalf("search attribute %q is missing", testSearchAttr) + // Confirm workflow has search attribute and shows up in custom list query + listFilter := fmt.Sprintf("%s=%q", testSearchAttr, "foo") + workflowList, err := c.ListWorkflow(ctx, &workflowservice.ListWorkflowExecutionsRequest{ + Namespace: ts.GetDefaultNamespace(), + Query: listFilter, + }) + if err != nil { + t.Fatal(err) } - if saType != enums.INDEXED_VALUE_TYPE_KEYWORD { - t.Error("search attribute type does not match expected") + if numExecutions := len(workflowList.GetExecutions()); numExecutions != 1 { + t.Errorf("Expected list filter %q to return one workflow, got %d", listFilter, numExecutions) + } else { + searchAttrPayload, ok := workflowList.GetExecutions()[0].GetSearchAttributes().GetIndexedFields()[testSearchAttr] + if !ok { + t.Fatal("Workflow missing test search attr") + } + var searchAttrValue string + if err := converter.GetDefaultDataConverter().FromPayload(searchAttrPayload, &searchAttrValue); err != nil { + t.Fatal(err) + } + assert.Equal(t, "foo", searchAttrValue) } } @@ -330,6 +369,12 @@ func BenchmarkRunWorkflow(b *testing.B) { } } +func SearchAttrWorkflow(ctx workflow.Context, searchAttr string) error { + return workflow.UpsertSearchAttributes(ctx, map[string]interface{}{ + searchAttr: "foo", + }) +} + // Example workflow/activity // Greet implements a Temporal workflow that returns a salutation for a given subject. From f8ff2a7255f947b98f9abd64b57cca79160b3804 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Wed, 26 Jul 2023 15:52:36 -0400 Subject: [PATCH 26/48] Use errors.As to check for permission denied --- temporaltest/server_test.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/temporaltest/server_test.go b/temporaltest/server_test.go index 487557eef7d..0086b7d7542 100644 --- a/temporaltest/server_test.go +++ b/temporaltest/server_test.go @@ -28,14 +28,15 @@ package temporaltest_test import ( "context" + "errors" "fmt" - "strings" "testing" "time" "github.com/stretchr/testify/assert" "go.temporal.io/api/enums/v1" "go.temporal.io/api/operatorservice/v1" + "go.temporal.io/api/serviceerror" "go.temporal.io/api/workflowservice/v1" "go.temporal.io/sdk/activity" "go.temporal.io/sdk/client" @@ -43,6 +44,7 @@ import ( "go.temporal.io/sdk/interceptor" "go.temporal.io/sdk/worker" "go.temporal.io/sdk/workflow" + "google.golang.org/grpc/codes" "go.temporal.io/server/common/authorization" "go.temporal.io/server/common/config" @@ -224,9 +226,11 @@ func TestBaseServerOptions(t *testing.T) { t.Fatal("err must be non-nil") } - if !strings.Contains(err.Error(), authorization.RequestUnauthorized) { - t.Errorf("expected error %q, got %q", authorization.RequestUnauthorized, err) + permissionDeniedErr := &serviceerror.PermissionDenied{} + if !errors.As(err, &permissionDeniedErr) { + t.Errorf("expected error %T, got %T", permissionDeniedErr, err) } + assert.Equal(t, codes.PermissionDenied.String(), permissionDeniedErr.Status().Code().String()) } func TestClientWithCustomInterceptor(t *testing.T) { From 04bf716030b62b4afc8048b6bfa2ac3d4c6b6fe6 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Wed, 26 Jul 2023 16:04:38 -0400 Subject: [PATCH 27/48] Verify that a custom worker option was set --- temporaltest/server_test.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/temporaltest/server_test.go b/temporaltest/server_test.go index 0086b7d7542..41e259a22ad 100644 --- a/temporaltest/server_test.go +++ b/temporaltest/server_test.go @@ -119,6 +119,7 @@ func TestNewServer(t *testing.T) { func TestNewWorkerWithOptions(t *testing.T) { ts := temporaltest.NewServer(temporaltest.WithT(t)) + c := ts.GetDefaultClient() ts.NewWorkerWithOptions( "hello_world", @@ -128,13 +129,16 @@ func TestNewWorkerWithOptions(t *testing.T) { worker.Options{ MaxConcurrentActivityExecutionSize: 1, MaxConcurrentLocalActivityExecutionSize: 1, + // We will later verify this option was set by checking the identity of the task queue poller. + Identity: "test-worker-with-options", }, ) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - wfr, err := ts.GetDefaultClient().ExecuteWorkflow( + // Verify that workflows still run to completion + wfr, err := c.ExecuteWorkflow( ctx, client.StartWorkflowOptions{TaskQueue: "hello_world"}, Greet, @@ -148,11 +152,17 @@ func TestNewWorkerWithOptions(t *testing.T) { if err := wfr.Get(ctx, &result); err != nil { t.Fatal(err) } - if result != "Hello world" { t.Fatalf("unexpected result: %q", result) } + // Verify that the Identity worker option was set. + resp, err := c.DescribeTaskQueue(ctx, "hello_world", enums.TASK_QUEUE_TYPE_WORKFLOW) + if err != nil { + t.Fatal(err) + } + poller := resp.GetPollers()[0] + assert.Equal(t, "test-worker-with-options", poller.GetIdentity()) } func TestDefaultWorkerOptions(t *testing.T) { From f9d60cf631d0b7ac42b6dbeb9b0ca358235414df Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Wed, 26 Jul 2023 16:20:53 -0400 Subject: [PATCH 28/48] Add TODO to investigate whether randomized db name is necessary in shared cache mode --- internal/temporalite/lite_server.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/temporalite/lite_server.go b/internal/temporalite/lite_server.go index 0adfa6db57f..3aab6df1d13 100644 --- a/internal/temporalite/lite_server.go +++ b/internal/temporalite/lite_server.go @@ -102,6 +102,8 @@ func (cfg *LiteServerConfig) apply(serverConfig *config.Config, provider *portPr if cfg.Ephemeral { sqliteConfig.ConnectAttributes["mode"] = "memory" sqliteConfig.ConnectAttributes["cache"] = "shared" + // TODO(jlegrone): investigate whether a randomized db name is necessary when running in shared cache mode: + // https://www.sqlite.org/sharedcache.html sqliteConfig.DatabaseName = fmt.Sprintf("%d", rand.Intn(9999999)) } else { sqliteConfig.ConnectAttributes["mode"] = "rwc" From 5a39decd46d984ef248ecc4026324c7c8a2fe01b Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Wed, 26 Jul 2023 16:22:44 -0400 Subject: [PATCH 29/48] Return error instead of panic while getting port --- internal/temporalite/freeport.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/temporalite/freeport.go b/internal/temporalite/freeport.go index 6f40bc97f0a..e9ad559b98d 100644 --- a/internal/temporalite/freeport.go +++ b/internal/temporalite/freeport.go @@ -46,7 +46,7 @@ func (p *portProvider) GetFreePort() (int, error) { addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") if err != nil { if addr, err = net.ResolveTCPAddr("tcp6", "[::1]:0"); err != nil { - panic(fmt.Sprintf("temporalite: failed to get free port: %v", err)) + return 0, fmt.Errorf("failed to get free port: %w", err) } } From ac773d35f91c6364122c99a19d70a5aa571ac639 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Wed, 26 Jul 2023 16:33:36 -0400 Subject: [PATCH 30/48] Update freeport.go to be unique implementation --- internal/temporalite/freeport.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/internal/temporalite/freeport.go b/internal/temporalite/freeport.go index e9ad559b98d..2555169c070 100644 --- a/internal/temporalite/freeport.go +++ b/internal/temporalite/freeport.go @@ -4,8 +4,6 @@ // // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. // -// Copyright (c) 2020 Uber Technologies, Inc. -// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights @@ -31,8 +29,6 @@ import ( "net" ) -// Modified from https://github.com/phayes/freeport/blob/95f893ade6f232a5f1511d61735d89b1ae2df543/freeport.go - func newPortProvider() *portProvider { return &portProvider{} } @@ -41,7 +37,7 @@ type portProvider struct { listeners []*net.TCPListener } -// GetFreePort asks the kernel for a free open port that is ready to use. +// GetFreePort finds an open port on the system which is ready to use. func (p *portProvider) GetFreePort() (int, error) { addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") if err != nil { @@ -60,6 +56,7 @@ func (p *portProvider) GetFreePort() (int, error) { return l.Addr().(*net.TCPAddr).Port, nil } +// MustGetFreePort calls GetFreePort, panicking on error. func (p *portProvider) MustGetFreePort() int { port, err := p.GetFreePort() if err != nil { From ff3ebf31a9ffecfad000abcc1d3700c470e41f90 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Wed, 26 Jul 2023 16:48:56 -0400 Subject: [PATCH 31/48] Remove temporaltest from UNIT_TEST_DIRS --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 50c4c624fc4..73d5b1754e9 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ FUNCTIONAL_TEST_NDC_ROOT := ./tests/ndc DB_INTEGRATION_TEST_ROOT := ./common/persistence/tests DB_TOOL_INTEGRATION_TEST_ROOT := ./tools/tests INTEGRATION_TEST_DIRS := $(DB_INTEGRATION_TEST_ROOT) $(DB_TOOL_INTEGRATION_TEST_ROOT) ./temporaltest -UNIT_TEST_DIRS := $(filter-out $(FUNCTIONAL_TEST_ROOT)% $(FUNCTIONAL_TEST_XDC_ROOT)% $(FUNCTIONAL_TEST_NDC_ROOT)% $(DB_INTEGRATION_TEST_ROOT)% $(DB_TOOL_INTEGRATION_TEST_ROOT)%,$(TEST_DIRS)) +UNIT_TEST_DIRS := $(filter-out $(FUNCTIONAL_TEST_ROOT)% $(FUNCTIONAL_TEST_XDC_ROOT)% $(FUNCTIONAL_TEST_NDC_ROOT)% $(INTEGRATION_TEST_DIRS)%,$(TEST_DIRS)) # github.com/urfave/cli/v2@v2.4.0 - needs to accept comma in values before unlocking https://github.com/urfave/cli/pull/1241. PINNED_DEPENDENCIES := \ From 8d29d2756b1d793e744686e3a633df59deda550e Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Wed, 26 Jul 2023 17:09:43 -0400 Subject: [PATCH 32/48] Skip conditional check when appending opts --- internal/temporalite/lite_server.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/temporalite/lite_server.go b/internal/temporalite/lite_server.go index 3aab6df1d13..2df4b9cc7ef 100644 --- a/internal/temporalite/lite_server.go +++ b/internal/temporalite/lite_server.go @@ -302,9 +302,8 @@ func NewLiteServer(liteConfig *LiteServerConfig, opts ...temporal.ServerOption) serverOpts = append(serverOpts, temporal.WithDynamicConfigClient(liteConfig.DynamicConfig)) } - if len(opts) > 0 { - serverOpts = append(serverOpts, opts...) - } + // Apply options from arguments + serverOpts = append(serverOpts, opts...) srv, err := temporal.NewServer(serverOpts...) if err != nil { From cf56460f77c49a0b2cd0c779e0022e9751b51524 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Wed, 26 Jul 2023 17:24:30 -0400 Subject: [PATCH 33/48] Remove godoc comment about overriding ConnectionOptions --- internal/temporalite/lite_server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/temporalite/lite_server.go b/internal/temporalite/lite_server.go index 2df4b9cc7ef..2d343d20cba 100644 --- a/internal/temporalite/lite_server.go +++ b/internal/temporalite/lite_server.go @@ -342,7 +342,7 @@ func (s *LiteServer) NewClient(ctx context.Context, namespace string) (client.Cl // // To set the client's namespace, use the corresponding field in client.Options. // -// Note that the HostPort and ConnectionOptions fields of client.Options will always be overridden. +// Note that options.HostPort will always be overridden. func (s *LiteServer) NewClientWithOptions(ctx context.Context, options client.Options) (client.Client, error) { options.HostPort = s.frontendHostPort return client.NewClient(options) From 9a0e082593f742af7631020d3788c40336f52ffc Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Wed, 26 Jul 2023 17:30:19 -0400 Subject: [PATCH 34/48] Use maps.Keys --- internal/temporalite/lite_server.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/temporalite/lite_server.go b/internal/temporalite/lite_server.go index 2d343d20cba..d439d497e39 100644 --- a/internal/temporalite/lite_server.go +++ b/internal/temporalite/lite_server.go @@ -40,6 +40,7 @@ import ( "time" "go.temporal.io/sdk/client" + "golang.org/x/exp/maps" "go.temporal.io/server/common/authorization" "go.temporal.io/server/common/cluster" @@ -362,10 +363,7 @@ var supportedPragmas = map[string]struct{}{ } func getAllowedPragmas() []string { - var allowedPragmaList []string - for k := range supportedPragmas { - allowedPragmaList = append(allowedPragmaList, k) - } + allowedPragmaList := maps.Keys(supportedPragmas) sort.Strings(allowedPragmaList) return allowedPragmaList } From 4d1d04be268aa548b887c7dc84e056228244aad6 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Wed, 26 Jul 2023 17:33:12 -0400 Subject: [PATCH 35/48] Don't set MembershipPort twice --- internal/temporalite/lite_server.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/temporalite/lite_server.go b/internal/temporalite/lite_server.go index d439d497e39..208261c819b 100644 --- a/internal/temporalite/lite_server.go +++ b/internal/temporalite/lite_server.go @@ -372,7 +372,7 @@ func (cfg *LiteServerConfig) mustGetService(frontendPortOffset int, provider *po svc := config.Service{ RPC: config.RPC{ GRPCPort: cfg.FrontendPort + frontendPortOffset, - MembershipPort: cfg.FrontendPort + 100 + frontendPortOffset, + MembershipPort: provider.MustGetFreePort(), BindOnLocalHost: true, BindOnIP: "", }, @@ -382,7 +382,6 @@ func (cfg *LiteServerConfig) mustGetService(frontendPortOffset int, provider *po if frontendPortOffset != 0 { svc.RPC.GRPCPort = provider.MustGetFreePort() } - svc.RPC.MembershipPort = provider.MustGetFreePort() // Optionally bind frontend to IPv4 address if frontendPortOffset == 0 && cfg.FrontendIP != "" { From 4f4070caa507bb4490585e9d8df2056c179a6eed Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Wed, 26 Jul 2023 17:37:22 -0400 Subject: [PATCH 36/48] Add log.Logger type assertion --- temporaltest/logger.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/temporaltest/logger.go b/temporaltest/logger.go index c9aa5b426f0..44ecd7c8977 100644 --- a/temporaltest/logger.go +++ b/temporaltest/logger.go @@ -28,8 +28,15 @@ package temporaltest import ( "testing" + + "go.temporal.io/sdk/log" ) +var _ log.Logger = &testLogger{} + +// testLogger implements a Go SDK logger by writing to the test output. +// +// Text will be printed only if the test fails or the -test.v flag is set. type testLogger struct { t *testing.T } From d240ccfcb40a4906463e03d287cb519c3c348353 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Wed, 26 Jul 2023 17:42:09 -0400 Subject: [PATCH 37/48] Simplify TestServerOption applyFunc --- temporaltest/options.go | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/temporaltest/options.go b/temporaltest/options.go index 3a6577c7fb1..21c8676a7cb 100644 --- a/temporaltest/options.go +++ b/temporaltest/options.go @@ -31,6 +31,7 @@ import ( "go.temporal.io/sdk/client" "go.temporal.io/sdk/worker" + "go.temporal.io/server/temporal" ) @@ -38,19 +39,23 @@ type TestServerOption interface { apply(*TestServer) } +type applyFunc func(*TestServer) + +func (f applyFunc) apply(s *TestServer) { f(s) } + // WithT directs all worker and client logs to the test logger. // // If this option is specified, then server will automatically be stopped when the // test completes. func WithT(t *testing.T) TestServerOption { - return newApplyFuncContainer(func(server *TestServer) { + return applyFunc(func(server *TestServer) { server.t = t }) } // WithBaseClientOptions configures options for the default clients and workers connected to the test server. func WithBaseClientOptions(o client.Options) TestServerOption { - return newApplyFuncContainer(func(server *TestServer) { + return applyFunc(func(server *TestServer) { server.defaultClientOptions = o }) } @@ -61,28 +66,14 @@ func WithBaseClientOptions(o client.Options) TestServerOption { // fail fast when workflow code panics or detects non-determinism. func WithBaseWorkerOptions(o worker.Options) TestServerOption { o.WorkflowPanicPolicy = worker.FailWorkflow - return newApplyFuncContainer(func(server *TestServer) { + return applyFunc(func(server *TestServer) { server.defaultWorkerOptions = o }) } // WithBaseServerOptions enables configuring additional server options not directly exposed via temporaltest. func WithBaseServerOptions(options ...temporal.ServerOption) TestServerOption { - return newApplyFuncContainer(func(server *TestServer) { + return applyFunc(func(server *TestServer) { server.serverOptions = append(server.serverOptions, options...) }) } - -type applyFuncContainer struct { - applyInternal func(*TestServer) -} - -func (fso *applyFuncContainer) apply(ts *TestServer) { - fso.applyInternal(ts) -} - -func newApplyFuncContainer(apply func(*TestServer)) *applyFuncContainer { - return &applyFuncContainer{ - applyInternal: apply, - } -} From 4ecfb551d5e24a45e62cbd1690c8b939b7af9f70 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Wed, 26 Jul 2023 17:45:08 -0400 Subject: [PATCH 38/48] Delegate to NewWorkerWithOptions --- temporaltest/server.go | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/temporaltest/server.go b/temporaltest/server.go index 70b3f87ec5d..b0e0abd4ea8 100644 --- a/temporaltest/server.go +++ b/temporaltest/server.go @@ -68,15 +68,7 @@ func (ts *TestServer) fatal(err error) { // NewWorker registers and starts a Temporal worker on the specified task queue. func (ts *TestServer) NewWorker(taskQueue string, registerFunc func(registry worker.Registry)) worker.Worker { - w := worker.New(ts.GetDefaultClient(), taskQueue, ts.defaultWorkerOptions) - registerFunc(w) - ts.workers = append(ts.workers, w) - - if err := w.Start(); err != nil { - ts.fatal(err) - } - - return w + return ts.NewWorkerWithOptions(taskQueue, registerFunc, ts.defaultWorkerOptions) } // NewWorkerWithOptions returns a Temporal worker on the specified task queue. From 0da81175a7030657ae9b87f795e18ae142bccdbd Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Wed, 26 Jul 2023 17:55:38 -0400 Subject: [PATCH 39/48] Start server synchronously --- temporaltest/server.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/temporaltest/server.go b/temporaltest/server.go index b0e0abd4ea8..b133105a458 100644 --- a/temporaltest/server.go +++ b/temporaltest/server.go @@ -146,7 +146,7 @@ func (ts *TestServer) Stop() { // the server and release resources. func NewServer(opts ...TestServerOption) *TestServer { rand.Seed(time.Now().UnixNano()) - testNamespace := fmt.Sprintf("temporaltest-%d", rand.Intn(999999)) + testNamespace := fmt.Sprintf("temporaltest-%d", rand.Intn(1e6)) ts := TestServer{ defaultTestNamespace: testNamespace, @@ -158,9 +158,7 @@ func NewServer(opts ...TestServerOption) *TestServer { } if ts.t != nil { - ts.t.Cleanup(func() { - ts.Stop() - }) + ts.t.Cleanup(ts.Stop) } s, err := temporalite.NewLiteServer(&temporalite.LiteServerConfig{ @@ -178,11 +176,10 @@ func NewServer(opts ...TestServerOption) *TestServer { } ts.server = s - go func() { - if err := s.Start(); err != nil { - ts.fatal(fmt.Errorf("error starting server: %w", err)) - } - }() + // Start does not block as long as InterruptOn is unset. + if err := s.Start(); err != nil { + ts.fatal(err) + } return &ts } From 12bdf7ad5770d07ec86044881bd282c0e886a1b7 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Wed, 26 Jul 2023 17:57:09 -0400 Subject: [PATCH 40/48] Add internal/temporalite to integration tests --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 73d5b1754e9..21fd91e89af 100644 --- a/Makefile +++ b/Makefile @@ -80,7 +80,7 @@ FUNCTIONAL_TEST_XDC_ROOT := ./tests/xdc FUNCTIONAL_TEST_NDC_ROOT := ./tests/ndc DB_INTEGRATION_TEST_ROOT := ./common/persistence/tests DB_TOOL_INTEGRATION_TEST_ROOT := ./tools/tests -INTEGRATION_TEST_DIRS := $(DB_INTEGRATION_TEST_ROOT) $(DB_TOOL_INTEGRATION_TEST_ROOT) ./temporaltest +INTEGRATION_TEST_DIRS := $(DB_INTEGRATION_TEST_ROOT) $(DB_TOOL_INTEGRATION_TEST_ROOT) ./temporaltest ./internal/temporalite UNIT_TEST_DIRS := $(filter-out $(FUNCTIONAL_TEST_ROOT)% $(FUNCTIONAL_TEST_XDC_ROOT)% $(FUNCTIONAL_TEST_NDC_ROOT)% $(INTEGRATION_TEST_DIRS)%,$(TEST_DIRS)) # github.com/urfave/cli/v2@v2.4.0 - needs to accept comma in values before unlocking https://github.com/urfave/cli/pull/1241. From d233bed850e8626ea36f422b234ddc2894a7280b Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 8 Aug 2023 17:51:25 -0400 Subject: [PATCH 41/48] Flaky fixes --- temporaltest/server.go | 23 +++++++++++++++++++++++ temporaltest/server_test.go | 15 +++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/temporaltest/server.go b/temporaltest/server.go index b133105a458..3dd658ef5a4 100644 --- a/temporaltest/server.go +++ b/temporaltest/server.go @@ -167,6 +167,9 @@ func NewServer(opts ...TestServerOption) *TestServer { Logger: log.NewNoopLogger(), DynamicConfig: dynamicconfig.StaticClient{ dynamicconfig.ForceSearchAttributesCacheRefreshOnRead: []dynamicconfig.ConstrainedValue{{Value: true}}, + // Avoid potential race conditions in tests that describe task queues + dynamicconfig.MatchingNumTaskqueueReadPartitions: []dynamicconfig.ConstrainedValue{{Value: 1}}, + dynamicconfig.MatchingNumTaskqueueWritePartitions: []dynamicconfig.ConstrainedValue{{Value: 1}}, }, // Disable "accept incoming network connections?" prompt on macOS FrontendIP: "127.0.0.1", @@ -181,5 +184,25 @@ func NewServer(opts ...TestServerOption) *TestServer { ts.fatal(err) } + // This sleep helps avoid the following panic: + // + // === RUN TestClientWithCustomInterceptor + // logger.go:50: INFO Started Worker [Namespace temporaltest-90552 TaskQueue hello_world WorkerID 85975@COMP-KD49X2K6CH@] + // logger.go:50: DEBUG ExecuteActivity [Namespace temporaltest-90552 TaskQueue hello_world WorkerID 85975@COMP-KD49X2K6CH@ WorkflowType Greet WorkflowID 2aaac089-8950-4ba3-b910-6deb3a0f2325 RunID 128c9905-6098-4e31-b16a-b9f47b247845 Attempt 1 ActivityID 8 ActivityType PickGreeting] + // logger.go:50: INFO Stopped Worker [Namespace temporaltest-90552 TaskQueue hello_world WorkerID 85975@COMP-KD49X2K6CH@] + // panic: runtime error: invalid memory address or nil pointer dereference + // [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x2727b82] + // + // goroutine 1690307 [running]: + // github.com/temporalio/ringpop-go/swim.(*NodeLabels).Set(0x0?, {0x336eed7, 0xb}, {0xc06d9a2920, 0x5}) + // /Users/jacoblegrone/go/pkg/mod/github.com/temporalio/ringpop-go@v0.0.0-20230606200434-b5c079f412d3/swim/labels.go:175 +0x82 + // go.temporal.io/server/common/membership/ringpop.(*monitor).Start(0xc0b9c46870?) + // /Users/jacoblegrone/Development/github.com/temporalio/temporal/common/membership/ringpop/monitor.go:148 +0x68e + // created by go.temporal.io/server/service/matching.(*Service).Start + // /Users/jacoblegrone/Development/github.com/temporalio/temporal/service/matching/service.go:110 +0x1f5 + // FAIL go.temporal.io/server/temporaltest 486.783s + // FAIL + time.Sleep(10 * time.Millisecond) + return &ts } diff --git a/temporaltest/server_test.go b/temporaltest/server_test.go index 41e259a22ad..1fdbdafa4e3 100644 --- a/temporaltest/server_test.go +++ b/temporaltest/server_test.go @@ -88,6 +88,8 @@ func ExampleNewServer() { } func TestNewServer(t *testing.T) { + t.Parallel() + ts := temporaltest.NewServer(temporaltest.WithT(t)) ts.NewWorker("hello_world", func(registry worker.Registry) { @@ -118,6 +120,8 @@ func TestNewServer(t *testing.T) { } func TestNewWorkerWithOptions(t *testing.T) { + t.Parallel() + ts := temporaltest.NewServer(temporaltest.WithT(t)) c := ts.GetDefaultClient() @@ -166,6 +170,8 @@ func TestNewWorkerWithOptions(t *testing.T) { } func TestDefaultWorkerOptions(t *testing.T) { + t.Parallel() + ts := temporaltest.NewServer( temporaltest.WithT(t), temporaltest.WithBaseWorkerOptions( @@ -215,6 +221,8 @@ func (denyAllClaimMapper) GetClaims(*authorization.AuthInfo) (*authorization.Cla } func TestBaseServerOptions(t *testing.T) { + t.Parallel() + // This test verifies that we can set custom claim mappers and authorizers // with BaseServerOptions. ts := temporaltest.NewServer( @@ -244,6 +252,8 @@ func TestBaseServerOptions(t *testing.T) { } func TestClientWithCustomInterceptor(t *testing.T) { + t.Parallel() + var opts client.Options opts.Interceptors = append(opts.Interceptors, NewTestInterceptor()) ts := temporaltest.NewServer( @@ -282,6 +292,8 @@ func TestClientWithCustomInterceptor(t *testing.T) { } func TestSearchAttributeRegistration(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() ts := temporaltest.NewServer(temporaltest.WithT(t)) @@ -328,6 +340,9 @@ func TestSearchAttributeRegistration(t *testing.T) { if err := wfr.Get(ctx, nil); err != nil { t.Fatal(err) } + // Wait a bit longer for the workflow to be indexed. This sleep usually isn't necessary, + // but helps avoid test flakiness. + time.Sleep(10 * time.Millisecond) // Confirm workflow has search attribute and shows up in custom list query listFilter := fmt.Sprintf("%s=%q", testSearchAttr, "foo") workflowList, err := c.ListWorkflow(ctx, &workflowservice.ListWorkflowExecutionsRequest{ From 364b2db3f7bbfb1e674a7c5e3b2264dd4fd1879f Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 8 Aug 2023 18:09:47 -0400 Subject: [PATCH 42/48] Remove deprecated rand.Seed call --- temporaltest/server.go | 1 - 1 file changed, 1 deletion(-) diff --git a/temporaltest/server.go b/temporaltest/server.go index 3dd658ef5a4..b7f55d467bf 100644 --- a/temporaltest/server.go +++ b/temporaltest/server.go @@ -145,7 +145,6 @@ func (ts *TestServer) Stop() { // If not specifying the WithT option, the caller should execute Stop when finished to close // the server and release resources. func NewServer(opts ...TestServerOption) *TestServer { - rand.Seed(time.Now().UnixNano()) testNamespace := fmt.Sprintf("temporaltest-%d", rand.Intn(1e6)) ts := TestServer{ From af4f9639ee272fb342c446ae14ba412765fd32eb Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 8 Aug 2023 18:40:28 -0400 Subject: [PATCH 43/48] WIP --- temporaltest/server.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/temporaltest/server.go b/temporaltest/server.go index b7f55d467bf..f8015187caa 100644 --- a/temporaltest/server.go +++ b/temporaltest/server.go @@ -167,8 +167,8 @@ func NewServer(opts ...TestServerOption) *TestServer { DynamicConfig: dynamicconfig.StaticClient{ dynamicconfig.ForceSearchAttributesCacheRefreshOnRead: []dynamicconfig.ConstrainedValue{{Value: true}}, // Avoid potential race conditions in tests that describe task queues - dynamicconfig.MatchingNumTaskqueueReadPartitions: []dynamicconfig.ConstrainedValue{{Value: 1}}, - dynamicconfig.MatchingNumTaskqueueWritePartitions: []dynamicconfig.ConstrainedValue{{Value: 1}}, + // dynamicconfig.MatchingNumTaskqueueReadPartitions: []dynamicconfig.ConstrainedValue{{Value: 1}}, + // dynamicconfig.MatchingNumTaskqueueWritePartitions: []dynamicconfig.ConstrainedValue{{Value: 1}}, }, // Disable "accept incoming network connections?" prompt on macOS FrontendIP: "127.0.0.1", @@ -201,7 +201,7 @@ func NewServer(opts ...TestServerOption) *TestServer { // /Users/jacoblegrone/Development/github.com/temporalio/temporal/service/matching/service.go:110 +0x1f5 // FAIL go.temporal.io/server/temporaltest 486.783s // FAIL - time.Sleep(10 * time.Millisecond) + time.Sleep(100 * time.Millisecond) return &ts } From 67032f06db2a8b226162ca26b76756d5b42fe4d4 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Wed, 16 Aug 2023 16:09:16 -0400 Subject: [PATCH 44/48] Use Eventually to speed up TestSearchAttributeRegistration Also updated comments in server.go --- temporaltest/server.go | 22 +---------------- temporaltest/server_test.go | 48 ++++++++++++++++++++----------------- 2 files changed, 27 insertions(+), 43 deletions(-) diff --git a/temporaltest/server.go b/temporaltest/server.go index f8015187caa..11c4b51f997 100644 --- a/temporaltest/server.go +++ b/temporaltest/server.go @@ -166,9 +166,6 @@ func NewServer(opts ...TestServerOption) *TestServer { Logger: log.NewNoopLogger(), DynamicConfig: dynamicconfig.StaticClient{ dynamicconfig.ForceSearchAttributesCacheRefreshOnRead: []dynamicconfig.ConstrainedValue{{Value: true}}, - // Avoid potential race conditions in tests that describe task queues - // dynamicconfig.MatchingNumTaskqueueReadPartitions: []dynamicconfig.ConstrainedValue{{Value: 1}}, - // dynamicconfig.MatchingNumTaskqueueWritePartitions: []dynamicconfig.ConstrainedValue{{Value: 1}}, }, // Disable "accept incoming network connections?" prompt on macOS FrontendIP: "127.0.0.1", @@ -183,24 +180,7 @@ func NewServer(opts ...TestServerOption) *TestServer { ts.fatal(err) } - // This sleep helps avoid the following panic: - // - // === RUN TestClientWithCustomInterceptor - // logger.go:50: INFO Started Worker [Namespace temporaltest-90552 TaskQueue hello_world WorkerID 85975@COMP-KD49X2K6CH@] - // logger.go:50: DEBUG ExecuteActivity [Namespace temporaltest-90552 TaskQueue hello_world WorkerID 85975@COMP-KD49X2K6CH@ WorkflowType Greet WorkflowID 2aaac089-8950-4ba3-b910-6deb3a0f2325 RunID 128c9905-6098-4e31-b16a-b9f47b247845 Attempt 1 ActivityID 8 ActivityType PickGreeting] - // logger.go:50: INFO Stopped Worker [Namespace temporaltest-90552 TaskQueue hello_world WorkerID 85975@COMP-KD49X2K6CH@] - // panic: runtime error: invalid memory address or nil pointer dereference - // [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x2727b82] - // - // goroutine 1690307 [running]: - // github.com/temporalio/ringpop-go/swim.(*NodeLabels).Set(0x0?, {0x336eed7, 0xb}, {0xc06d9a2920, 0x5}) - // /Users/jacoblegrone/go/pkg/mod/github.com/temporalio/ringpop-go@v0.0.0-20230606200434-b5c079f412d3/swim/labels.go:175 +0x82 - // go.temporal.io/server/common/membership/ringpop.(*monitor).Start(0xc0b9c46870?) - // /Users/jacoblegrone/Development/github.com/temporalio/temporal/common/membership/ringpop/monitor.go:148 +0x68e - // created by go.temporal.io/server/service/matching.(*Service).Start - // /Users/jacoblegrone/Development/github.com/temporalio/temporal/service/matching/service.go:110 +0x1f5 - // FAIL go.temporal.io/server/temporaltest 486.783s - // FAIL + // This sleep helps avoid a panic in github.com/temporalio/ringpop-go@v0.0.0-20230606200434-b5c079f412d3/swim/labels.go:175 time.Sleep(100 * time.Millisecond) return &ts diff --git a/temporaltest/server_test.go b/temporaltest/server_test.go index 1fdbdafa4e3..cf1f24eb8a6 100644 --- a/temporaltest/server_test.go +++ b/temporaltest/server_test.go @@ -340,31 +340,35 @@ func TestSearchAttributeRegistration(t *testing.T) { if err := wfr.Get(ctx, nil); err != nil { t.Fatal(err) } - // Wait a bit longer for the workflow to be indexed. This sleep usually isn't necessary, + + // Wait a bit longer for the workflow to be indexed. This usually isn't necessary, // but helps avoid test flakiness. - time.Sleep(10 * time.Millisecond) - // Confirm workflow has search attribute and shows up in custom list query - listFilter := fmt.Sprintf("%s=%q", testSearchAttr, "foo") - workflowList, err := c.ListWorkflow(ctx, &workflowservice.ListWorkflowExecutionsRequest{ - Namespace: ts.GetDefaultNamespace(), - Query: listFilter, - }) - if err != nil { - t.Fatal(err) - } - if numExecutions := len(workflowList.GetExecutions()); numExecutions != 1 { - t.Errorf("Expected list filter %q to return one workflow, got %d", listFilter, numExecutions) - } else { - searchAttrPayload, ok := workflowList.GetExecutions()[0].GetSearchAttributes().GetIndexedFields()[testSearchAttr] - if !ok { - t.Fatal("Workflow missing test search attr") - } - var searchAttrValue string - if err := converter.GetDefaultDataConverter().FromPayload(searchAttrPayload, &searchAttrValue); err != nil { + assert.Eventually(t, func() bool { + // Confirm workflow has search attribute and shows up in custom list query + listFilter := fmt.Sprintf("%s=%q", testSearchAttr, "foo") + workflowList, err := c.ListWorkflow(ctx, &workflowservice.ListWorkflowExecutionsRequest{ + Namespace: ts.GetDefaultNamespace(), + Query: listFilter, + }) + if err != nil { t.Fatal(err) } - assert.Equal(t, "foo", searchAttrValue) - } + if numExecutions := len(workflowList.GetExecutions()); numExecutions != 1 { + t.Logf("Expected list filter %q to return one workflow, got %d", listFilter, numExecutions) + return false + } else { + searchAttrPayload, ok := workflowList.GetExecutions()[0].GetSearchAttributes().GetIndexedFields()[testSearchAttr] + if !ok { + t.Fatal("Workflow missing test search attr") + } + var searchAttrValue string + if err := converter.GetDefaultDataConverter().FromPayload(searchAttrPayload, &searchAttrValue); err != nil { + t.Fatal(err) + } + assert.Equal(t, "foo", searchAttrValue) + } + return true + }, 30*time.Second, 100*time.Millisecond) } func BenchmarkRunWorkflow(b *testing.B) { From 211c9af9fd44926d542b248b337f1f51db7a58f0 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 17 Oct 2023 13:16:15 -0400 Subject: [PATCH 45/48] Fix lint warnings: add error handling on test server shutdown --- internal/temporalite/lite_server.go | 6 +++--- temporaltest/server.go | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/temporalite/lite_server.go b/internal/temporalite/lite_server.go index 208261c819b..53843c0afee 100644 --- a/internal/temporalite/lite_server.go +++ b/internal/temporalite/lite_server.go @@ -327,10 +327,10 @@ func (s *LiteServer) Start() error { } // Stop the server. -func (s *LiteServer) Stop() { +func (s *LiteServer) Stop() error { // We wrap Server instead of simply embedding it in the LiteServer struct so // that it's possible to add additional lifecycle hooks here if necessary. - s.internal.Stop() + return s.internal.Stop() } // NewClient initializes a client ready to communicate with the Temporal @@ -346,7 +346,7 @@ func (s *LiteServer) NewClient(ctx context.Context, namespace string) (client.Cl // Note that options.HostPort will always be overridden. func (s *LiteServer) NewClientWithOptions(ctx context.Context, options client.Options) (client.Client, error) { options.HostPort = s.frontendHostPort - return client.NewClient(options) + return client.Dial(options) } // FrontendHostPort returns the host:port for this server. diff --git a/temporaltest/server.go b/temporaltest/server.go index 11c4b51f997..c0edcf69cd4 100644 --- a/temporaltest/server.go +++ b/temporaltest/server.go @@ -137,7 +137,11 @@ func (ts *TestServer) Stop() { for _, c := range ts.clients { c.Close() } - ts.server.Stop() + if err := ts.server.Stop(); err != nil { + // Log instead of throwing error because there's no need to fail the test + // if it already succeeded. + ts.t.Logf("error shutting down Temporal server: %s", err) + } } // NewServer starts and returns a new TestServer. From 5d217dddefb9e168dbde49995050f8ab0d9c184c Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 17 Oct 2023 13:18:33 -0400 Subject: [PATCH 46/48] Inline useless else branch --- temporaltest/server_test.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/temporaltest/server_test.go b/temporaltest/server_test.go index cf1f24eb8a6..60db990afc8 100644 --- a/temporaltest/server_test.go +++ b/temporaltest/server_test.go @@ -356,17 +356,18 @@ func TestSearchAttributeRegistration(t *testing.T) { if numExecutions := len(workflowList.GetExecutions()); numExecutions != 1 { t.Logf("Expected list filter %q to return one workflow, got %d", listFilter, numExecutions) return false - } else { - searchAttrPayload, ok := workflowList.GetExecutions()[0].GetSearchAttributes().GetIndexedFields()[testSearchAttr] - if !ok { - t.Fatal("Workflow missing test search attr") - } - var searchAttrValue string - if err := converter.GetDefaultDataConverter().FromPayload(searchAttrPayload, &searchAttrValue); err != nil { - t.Fatal(err) - } - assert.Equal(t, "foo", searchAttrValue) } + + searchAttrPayload, ok := workflowList.GetExecutions()[0].GetSearchAttributes().GetIndexedFields()[testSearchAttr] + if !ok { + t.Fatal("Workflow missing test search attr") + } + var searchAttrValue string + if err := converter.GetDefaultDataConverter().FromPayload(searchAttrPayload, &searchAttrValue); err != nil { + t.Fatal(err) + } + assert.Equal(t, "foo", searchAttrValue) + return true }, 30*time.Second, 100*time.Millisecond) } From a936d52b48210a7d9859d64bd89a1099cf087bd0 Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 17 Oct 2023 14:01:23 -0400 Subject: [PATCH 47/48] Fix unit test filter --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7060381e1a3..e7fe322b424 100644 --- a/Makefile +++ b/Makefile @@ -79,7 +79,7 @@ FUNCTIONAL_TEST_NDC_ROOT := ./tests/ndc DB_INTEGRATION_TEST_ROOT := ./common/persistence/tests DB_TOOL_INTEGRATION_TEST_ROOT := ./tools/tests INTEGRATION_TEST_DIRS := $(DB_INTEGRATION_TEST_ROOT) $(DB_TOOL_INTEGRATION_TEST_ROOT) ./temporaltest ./internal/temporalite -UNIT_TEST_DIRS := $(filter-out $(FUNCTIONAL_TEST_ROOT)% $(FUNCTIONAL_TEST_XDC_ROOT)% $(FUNCTIONAL_TEST_NDC_ROOT)% $(INTEGRATION_TEST_DIRS)%,$(TEST_DIRS)) +UNIT_TEST_DIRS := $(filter-out $(FUNCTIONAL_TEST_ROOT)% $(FUNCTIONAL_TEST_XDC_ROOT)% $(FUNCTIONAL_TEST_NDC_ROOT)% $(DB_INTEGRATION_TEST_ROOT)% $(DB_TOOL_INTEGRATION_TEST_ROOT)% ./temporaltest% ./internal/temporalite%,$(TEST_DIRS)) # github.com/urfave/cli/v2@v2.4.0 - needs to accept comma in values before unlocking https://github.com/urfave/cli/pull/1241. PINNED_DEPENDENCIES := \ From 79883a7e33cd6cccd28a18580cc1c91733fab3db Mon Sep 17 00:00:00 2001 From: Jacob LeGrone Date: Tue, 17 Oct 2023 14:16:11 -0400 Subject: [PATCH 48/48] Remove parallelism from temporaltest tests to avoid tripping race detector --- temporaltest/server_test.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/temporaltest/server_test.go b/temporaltest/server_test.go index 60db990afc8..7694dc48f96 100644 --- a/temporaltest/server_test.go +++ b/temporaltest/server_test.go @@ -88,8 +88,6 @@ func ExampleNewServer() { } func TestNewServer(t *testing.T) { - t.Parallel() - ts := temporaltest.NewServer(temporaltest.WithT(t)) ts.NewWorker("hello_world", func(registry worker.Registry) { @@ -120,8 +118,6 @@ func TestNewServer(t *testing.T) { } func TestNewWorkerWithOptions(t *testing.T) { - t.Parallel() - ts := temporaltest.NewServer(temporaltest.WithT(t)) c := ts.GetDefaultClient() @@ -170,8 +166,6 @@ func TestNewWorkerWithOptions(t *testing.T) { } func TestDefaultWorkerOptions(t *testing.T) { - t.Parallel() - ts := temporaltest.NewServer( temporaltest.WithT(t), temporaltest.WithBaseWorkerOptions( @@ -221,8 +215,6 @@ func (denyAllClaimMapper) GetClaims(*authorization.AuthInfo) (*authorization.Cla } func TestBaseServerOptions(t *testing.T) { - t.Parallel() - // This test verifies that we can set custom claim mappers and authorizers // with BaseServerOptions. ts := temporaltest.NewServer( @@ -252,8 +244,6 @@ func TestBaseServerOptions(t *testing.T) { } func TestClientWithCustomInterceptor(t *testing.T) { - t.Parallel() - var opts client.Options opts.Interceptors = append(opts.Interceptors, NewTestInterceptor()) ts := temporaltest.NewServer( @@ -292,8 +282,6 @@ func TestClientWithCustomInterceptor(t *testing.T) { } func TestSearchAttributeRegistration(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() ts := temporaltest.NewServer(temporaltest.WithT(t))