Skip to content
This repository has been archived by the owner on Apr 3, 2024. It is now read-only.

Commit

Permalink
Dynamic config values and default search attribute cache as disabled (#…
Browse files Browse the repository at this point in the history
…136)

Fixes #127
  • Loading branch information
cretz committed Sep 23, 2022
1 parent 7800181 commit a3d9d73
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 15 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@ The UI can also be disabled via a runtime flag:
temporalite start --headless
```

### Dynamic Config

Some advanced uses require Temporal dynamic configuration values which are usually set via a dynamic configuration file inside the Temporal configuration file. Alternatively, dynamic configuration values can be set via `--dynamic-config-value KEY=JSON_VALUE`.

For example, to disable search attribute cache to make created search attributes available for use right away:

```bash
temporalite start --dynamic-config-value system.forceSearchAttributesCacheRefreshOnRead=true
```

## Known Issues

- When consuming Temporalite as a library in go mod, you may want to replace grpc-gateway with a fork to address URL escaping issue in UI. See <https://github.com/temporalio/temporalite/pull/118>
57 changes: 45 additions & 12 deletions cmd/temporalite/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package main

import (
"encoding/json"
"fmt"
goLog "log"
"net"
Expand All @@ -13,6 +14,7 @@ import (

"github.com/urfave/cli/v2"
"go.temporal.io/server/common/config"
"go.temporal.io/server/common/dynamicconfig"
"go.temporal.io/server/common/headers"
"go.temporal.io/server/common/log"
"go.temporal.io/server/temporal"
Expand All @@ -35,18 +37,19 @@ var (
)

const (
ephemeralFlag = "ephemeral"
dbPathFlag = "filename"
portFlag = "port"
metricsPortFlag = "metrics-port"
uiPortFlag = "ui-port"
headlessFlag = "headless"
ipFlag = "ip"
logFormatFlag = "log-format"
logLevelFlag = "log-level"
namespaceFlag = "namespace"
pragmaFlag = "sqlite-pragma"
configFlag = "config"
ephemeralFlag = "ephemeral"
dbPathFlag = "filename"
portFlag = "port"
metricsPortFlag = "metrics-port"
uiPortFlag = "ui-port"
headlessFlag = "headless"
ipFlag = "ip"
logFormatFlag = "log-format"
logLevelFlag = "log-level"
namespaceFlag = "namespace"
pragmaFlag = "sqlite-pragma"
configFlag = "config"
dynamicConfigValueFlag = "dynamic-config-value"
)

func init() {
Expand Down Expand Up @@ -146,6 +149,10 @@ func buildCLI() *cli.App {
EnvVars: []string{config.EnvKeyConfigDir},
Value: "",
},
&cli.StringSliceFlag{
Name: dynamicConfigValueFlag,
Usage: `dynamic config value, as KEY=JSON_VALUE (meaning strings need quotes)`,
},
},
Before: func(c *cli.Context) error {
if c.Args().Len() > 0 {
Expand Down Expand Up @@ -262,6 +269,14 @@ func buildCLI() *cli.App {
}
opts = append(opts, temporalite.WithLogger(logger))

configVals, err := getDynamicConfigValues(c.StringSlice(dynamicConfigValueFlag))
if err != nil {
return err
}
for k, v := range configVals {
opts = append(opts, temporalite.WithDynamicConfigValue(k, v))
}

s, err := temporalite.NewServer(opts...)
if err != nil {
return err
Expand Down Expand Up @@ -289,3 +304,21 @@ func getPragmaMap(input []string) (map[string]string, error) {
}
return result, nil
}

func getDynamicConfigValues(input []string) (map[dynamicconfig.Key][]dynamicconfig.ConstrainedValue, error) {
ret := make(map[dynamicconfig.Key][]dynamicconfig.ConstrainedValue, len(input))
for _, keyValStr := range input {
keyVal := strings.SplitN(keyValStr, "=", 2)
if len(keyVal) != 2 {
return nil, fmt.Errorf("dynamic config value not in KEY=JSON_VAL format")
}
key := dynamicconfig.Key(keyVal[0])
// We don't support constraints currently
var val dynamicconfig.ConstrainedValue
if err := json.Unmarshal([]byte(keyVal[1]), &val.Value); err != nil {
return nil, fmt.Errorf("invalid JSON value for key %q: %w", key, err)
}
ret[key] = append(ret[key], val)
}
return ret, nil
}
65 changes: 65 additions & 0 deletions cmd/temporalite/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// MIT License
//
// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved.
//
// Copyright (c) 2021 Datadog, 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 main

import (
"reflect"
"testing"
)

func TestGetDynamicConfigValues(t *testing.T) {
assertBadVal := func(v string) {
if _, err := getDynamicConfigValues([]string{v}); err == nil {
t.Fatalf("expected error for %v", v)
}
}
type v map[string][]interface{}
assertGoodVals := func(expected v, in ...string) {
actualVals, err := getDynamicConfigValues(in)
if err != nil {
t.Fatal(err)
}
actual := make(v, len(actualVals))
for k, vals := range actualVals {
for _, val := range vals {
actual[string(k)] = append(actual[string(k)], val.Value)
}
}
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("not equal, expected - actual: %v - %v", expected, actual)
}
}

assertBadVal("foo")
assertBadVal("foo=")
assertBadVal("foo=bar")
assertBadVal("foo=123a")

assertGoodVals(v{"foo": {123.0}}, "foo=123")
assertGoodVals(
v{"foo": {123.0, []interface{}{"123", false}}, "bar": {"baz"}, "qux": {true}},
"foo=123", `bar="baz"`, "qux=true", `foo=["123", false]`,
)
}
2 changes: 2 additions & 0 deletions internal/liteconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ 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"
Expand Down Expand Up @@ -59,6 +60,7 @@ type Config struct {
FrontendIP string
UIServer UIServer
BaseConfig *config.Config
DynamicConfig dynamicconfig.StaticClient
}

var SupportedPragmas = map[string]struct{}{
Expand Down
21 changes: 21 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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"

Expand Down Expand Up @@ -119,6 +120,26 @@ func WithBaseConfig(base *config.Config) ServerOption {
})
}

// WithDynamicConfigValue sets the given dynamic config key with the given set
// of values. This will overwrite the 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 disables search attribute caching. This
// delegates to WithDynamicConfigValue.
func WithSearchAttributeCacheDisabled() ServerOption {
return WithDynamicConfigValue(
dynamicconfig.ForceSearchAttributesCacheRefreshOnRead,
[]dynamicconfig.ConstrainedValue{{Value: true}},
)
}

type applyFuncContainer struct {
applyInternal func(*liteconfig.Config)
}
Expand Down
11 changes: 9 additions & 2 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
"go.temporal.io/sdk/client"
"go.temporal.io/server/common/authorization"
"go.temporal.io/server/common/config"
"go.temporal.io/server/common/dynamicconfig"
"go.temporal.io/server/schema/sqlite"
"go.temporal.io/server/temporal"

Expand Down Expand Up @@ -94,7 +93,15 @@ func NewServer(opts ...ServerOption) (*Server, error) {
temporal.WithClaimMapper(func(cfg *config.Config) authorization.ClaimMapper {
return claimMapper
}),
temporal.WithDynamicConfigClient(dynamicconfig.NewNoopClient()),
}

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.UpstreamOptions) > 0 {
Expand Down
1 change: 1 addition & 0 deletions temporaltest/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ func NewServer(opts ...TestServerOption) *TestServer {
temporalite.WithPersistenceDisabled(),
temporalite.WithDynamicPorts(),
temporalite.WithLogger(log.NewNoopLogger()),
temporalite.WithSearchAttributeCacheDisabled(),
)

s, err := temporalite.NewServer(ts.serverOptions...)
Expand Down
28 changes: 27 additions & 1 deletion temporaltest/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"testing"
"time"

"go.temporal.io/api/enums/v1"
"go.temporal.io/api/operatorservice/v1"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/worker"

Expand Down Expand Up @@ -134,7 +136,6 @@ func TestDefaultWorkerOptions(t *testing.T) {
ts.NewWorker("hello_world", func(registry worker.Registry) {
helloworld.RegisterWorkflowsAndActivities(registry)
})

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

Expand Down Expand Up @@ -196,6 +197,31 @@ func TestClientWithDefaultInterceptor(t *testing.T) {
}
}

func TestSearchAttributeCacheDisabled(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
ts := temporaltest.NewServer(temporaltest.WithT(t))

// Create a search attribute
_, err := ts.DefaultClient().OperatorService().AddSearchAttributes(ctx, &operatorservice.AddSearchAttributesRequest{
SearchAttributes: map[string]enums.IndexedValueType{
"my-search-attr": enums.INDEXED_VALUE_TYPE_TEXT,
},
})
if err != nil {
t.Fatal(err)
}

// Confirm it exists immediately
resp, err := ts.DefaultClient().GetSearchAttributes(ctx)
if err != nil {
t.Fatal(err)
}
if resp.Keys["my-search-attr"] != enums.INDEXED_VALUE_TYPE_TEXT {
t.Fatal("search attribute not found")
}
}

func BenchmarkRunWorkflow(b *testing.B) {
ts := temporaltest.NewServer()
defer ts.Stop()
Expand Down

0 comments on commit a3d9d73

Please sign in to comment.