diff --git a/Gopkg.lock b/Gopkg.lock index c7a268b2d..cc5e1548c 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,48 +2,69 @@ [[projects]] + digest = "1:b16fbfbcc20645cb419f78325bb2e85ec729b338e996a228124d68931a6f2a37" name = "github.com/BurntSushi/toml" packages = ["."] + pruneopts = "UT" revision = "b26d9c308763d68093482582cea63d69be07a0f0" version = "v0.3.0" [[projects]] branch = "master" + digest = "1:7afff364b8e5e9f1085fe77ae5630b8e0f7482338a50535881aa0b433e48fb0b" name = "github.com/alicebob/gopher-json" packages = ["."] + pruneopts = "UT" revision = "5a6b3ba71ee69b77cf64febf8b5a7526ca5eaef0" [[projects]] + branch = "master" + digest = "1:82df807c12721df81914d7cbb4885218ec21d38f7744b11ec28b521f15e1edb4" name = "github.com/alicebob/miniredis" packages = [ ".", - "server" + "server", ] + pruneopts = "UT" revision = "8890fdcfa933258e09a5b9b897270118064d70fd" - version = "2.4.4" [[projects]] branch = "master" + digest = "1:d6afaeed1502aa28e80a4ed0981d570ad91b2579193404256ce672ed0a609e0d" name = "github.com/beorn7/perks" packages = ["quantile"] + pruneopts = "UT" revision = "3a771d992973f24aa725d07868b467d1ddfceafb" [[projects]] + digest = "1:c28625428387b63dd7154eb857f51e700465cfbf7c06f619e71f2da33cefe47e" + name = "github.com/coreos/bbolt" + packages = ["."] + pruneopts = "UT" + revision = "583e8937c61f1af6513608ccc75c97b6abdf4ff9" + version = "v1.3.0" + +[[projects]] + digest = "1:db2e37856e0f3e8ad322792ca2ede9bcf821f4d482ca0ef300e6a9d85776a99a" name = "github.com/go-kit/kit" packages = [ "log", - "log/level" + "log/level", ] + pruneopts = "UT" revision = "ca4112baa34cb55091301bdc13b1420a122b1b9e" version = "v0.7.0" [[projects]] + digest = "1:31a18dae27a29aa074515e43a443abfd2ba6deb6d69309d8d7ce789c45f34659" name = "github.com/go-logfmt/logfmt" packages = ["."] + pruneopts = "UT" revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5" version = "v0.3.0" [[projects]] + digest = "1:6361fc2f37f6a779924d408a955883db8ae02255a26473517b03cd0a80f8cdd5" name = "github.com/go-redis/redis" packages = [ ".", @@ -53,136 +74,188 @@ "internal/pool", "internal/proto", "internal/singleflight", - "internal/util" + "internal/util", ] + pruneopts = "UT" revision = "877867d2845fbaf86798befe410b6ceb6f5c29a3" version = "v6.10.2" [[projects]] + digest = "1:c4a2528ccbcabf90f9f3c464a5fc9e302d592861bbfd0b7135a7de8a943d0406" name = "github.com/go-stack/stack" packages = ["."] + pruneopts = "UT" revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc" version = "v1.7.0" [[projects]] + digest = "1:ffc060c551980d37ee9e428ef528ee2813137249ccebb0bfc412ef83071cac91" name = "github.com/golang/protobuf" packages = ["proto"] + pruneopts = "UT" revision = "925541529c1fa6821df4e44ce2723319eb2be768" version = "v1.0.0" [[projects]] branch = "master" + digest = "1:29a5ab9fa9e845fd8e8726f31b187d710afd271ef1eb32085fe3d604b7e06382" name = "github.com/golang/snappy" packages = ["."] + pruneopts = "UT" revision = "553a641470496b2327abcac10b36396bd98e45c9" [[projects]] + digest = "1:38ec74012390146c45af1f92d46e5382b50531247929ff3a685d2b2be65155ac" name = "github.com/gomodule/redigo" packages = [ "internal", - "redis" + "redis", ] + pruneopts = "UT" revision = "9c11da706d9b7902c6da69c592f75637793fe121" version = "v2.0.0" [[projects]] + digest = "1:160eabf7a69910fd74f29c692718bc2437c1c1c7d4c9dea9712357752a70e5df" name = "github.com/gorilla/context" packages = ["."] + pruneopts = "UT" revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a" version = "v1.1" [[projects]] + digest = "1:185a43b59a1f4e7ad4e7ccafb8a1538193d897a2a75be16dda093ec42ad231cf" name = "github.com/gorilla/handlers" packages = ["."] + pruneopts = "UT" revision = "90663712d74cb411cbef281bc1e08c19d1a76145" version = "v1.3.0" [[projects]] + digest = "1:88aa9e326e2bd6045a46e00a922954b3e1a9ac5787109f49ac85366df370e1e5" name = "github.com/gorilla/mux" packages = ["."] + pruneopts = "UT" revision = "53c1911da2b537f792e7cafcb446b05ffe33b996" version = "v1.6.1" [[projects]] branch = "master" + digest = "1:a64e323dc06b73892e5bb5d040ced475c4645d456038333883f58934abbf6f72" name = "github.com/kr/logfmt" packages = ["."] + pruneopts = "UT" revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0" [[projects]] + digest = "1:5985ef4caf91ece5d54817c11ea25f182697534f8ae6521eadcd628c142ac4b6" name = "github.com/matttproud/golang_protobuf_extensions" packages = ["pbutil"] + pruneopts = "UT" revision = "3247c84500bff8d9fb6d579d800f20b3e091582c" version = "v1.0.0" [[projects]] + digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747" name = "github.com/pkg/errors" packages = ["."] + pruneopts = "UT" revision = "645ef00459ed84a119197bfb8d8205042c6df63d" version = "v0.8.0" [[projects]] branch = "master" + digest = "1:2331a3aa2a326722e1e808f131bcf0da9618399e763610e17da20ad25ecf2bfe" name = "github.com/prometheus/client_golang" packages = [ "prometheus", - "prometheus/promhttp" + "prometheus/promhttp", ] + pruneopts = "UT" revision = "d6a9817c4afc94d51115e4a30d449056a3fbf547" [[projects]] branch = "master" + digest = "1:32d10bdfa8f09ecf13598324dba86ab891f11db3c538b6a34d1c3b5b99d7c36b" name = "github.com/prometheus/client_model" packages = ["go"] + pruneopts = "UT" revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c" [[projects]] branch = "master" + digest = "1:d8eb54ba064d155389fdb417aca96e928404dd1a8054570da25c071096d2bd9b" name = "github.com/prometheus/common" packages = [ "expfmt", "internal/bitbucket.org/ww/goautoneg", - "model" + "model", ] + pruneopts = "UT" revision = "38c53a9f4bfcd932d1b00bfc65e256a7fba6b37a" [[projects]] branch = "master" + digest = "1:f261e077bd68fcb94edbc27ca4964fca036aebb79423f1e5cd7389df18e38b7f" name = "github.com/prometheus/procfs" packages = [ ".", "internal/util", "nfs", - "xfs" + "xfs", ] + pruneopts = "UT" revision = "780932d4fbbe0e69b84c34c20f5c8d0981e109ea" [[projects]] branch = "master" + digest = "1:0b3820dab04898566dda3451e758a4aef25cf35fd36ba46e123173c5ecd2ca5f" name = "github.com/yuin/gopher-lua" packages = [ ".", "ast", "parse", - "pm" + "pm", ] + pruneopts = "UT" revision = "46796da1b0b4794e1e341883a399f12cc7574b55" [[projects]] branch = "master" + digest = "1:99e9f259afc6328efa223a59cdcfac9f974a9708c5dc1512948c995069ed1a5b" name = "golang.org/x/sys" packages = ["unix"] + pruneopts = "UT" revision = "378d26f46672a356c46195c28f61bdb4c0a781dd" [[projects]] + digest = "1:c805e517269b0ba4c21ded5836019ed7d16953d4026cb7d00041d039c7906be9" name = "gopkg.in/natefinch/lumberjack.v2" packages = ["."] + pruneopts = "UT" revision = "a96e63847dc3c67d17befa69c303767e2f84e54f" version = "v2.1" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "547cf8110b1b7744f6a59abbdf8a1fc2bb52137164ef945aee72b8ad17b6c752" + input-imports = [ + "github.com/BurntSushi/toml", + "github.com/alicebob/miniredis", + "github.com/coreos/bbolt", + "github.com/go-kit/kit/log", + "github.com/go-kit/kit/log/level", + "github.com/go-redis/redis", + "github.com/go-stack/stack", + "github.com/golang/snappy", + "github.com/gorilla/handlers", + "github.com/gorilla/mux", + "github.com/pkg/errors", + "github.com/prometheus/client_golang/prometheus", + "github.com/prometheus/client_golang/prometheus/promhttp", + "github.com/prometheus/common/model", + "golang.org/x/sys/unix", + "gopkg.in/natefinch/lumberjack.v2", + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 13a41ceb8..421eb03d3 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -72,3 +72,11 @@ [prune] go-tests = true unused-packages = true + +[[constraint]] + branch = "master" + name = "github.com/alicebob/miniredis" + +[[constraint]] + name = "github.com/coreos/bbolt" + version = "1.3.0" diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 6484f6f39..888531e35 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -1,7 +1,11 @@ Maintainers of this repository with their focus areas: +From Comcast: * James Ranson @jranson: Core Functionality * Chris Randles @crandles: Core Functionality * Adam Ross @LimitlessEarth: Deployment-related matters (Makefile, Dockerfile, Helm Charts, etc) +Outside of Comcast: +* Julius Volz @juliusv: Core Functionality + You can tag all reviewers in issues/commits/PR's using the @trickster-reviewers handle. diff --git a/boltdb.go b/boltdb.go new file mode 100644 index 000000000..5a1a0cb9c --- /dev/null +++ b/boltdb.go @@ -0,0 +1,239 @@ +/** +* Copyright 2018 Comcast Cable Communications Management, LLC +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://www.apache.org/licenses/LICENSE-2.0 +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. + */ + +package main + +import ( + "fmt" + "path/filepath" + "strconv" + "strings" + "time" + + bolt "github.com/coreos/bbolt" + "github.com/go-kit/kit/log/level" +) + +// BoltDBCache describes a BoltDB Cache +type BoltDBCache struct { + T *TricksterHandler + Config BoltDBCacheConfig + dbh *bolt.DB +} + +// Connect instantiates the BoltDBCache mutex map and starts the Expired Entry Reaper goroutine +func (c *BoltDBCache) Connect() error { + + dir, _ := filepath.Split(c.Config.Filename) + + level.Info(c.T.Logger).Log("event", "boltdb cache setup", "cachePath", c.Config.Filename) + + err := makeDirectory(dir) + if err != nil { + return err + } + + c.dbh, err = bolt.Open(c.Config.Filename, 0644, &bolt.Options{Timeout: 1 * time.Second}) + if err != nil { + return err + } + + err = c.dbh.Update(func(tx *bolt.Tx) error { + tx.CreateBucketIfNotExists([]byte(c.Config.Bucket)) + if err != nil { + return fmt.Errorf("create bucket: %s", err) + } + return nil + }) + if err != nil { + return err + } + + go c.Reap() + return nil +} + +// Store places an object in the cache using the specified key and ttl +func (c *BoltDBCache) Store(cacheKey string, data string, ttl int64) error { + + expKey, dataKey := c.getKeyNames(cacheKey) + expiration := []byte(strconv.FormatInt(time.Now().Unix()+ttl, 10)) + + err := c.dbh.Update(func(tx *bolt.Tx) error { + + b := tx.Bucket([]byte(c.Config.Bucket)) + + err := b.Put([]byte(dataKey), []byte(data)) + if err != nil { + return err + } + + return b.Put([]byte(expKey), expiration) + }) + if err != nil { + return err + } + + level.Debug(c.T.Logger).Log("event", "boltdb cache store", "key", dataKey, "expKey", expKey) + + return nil +} + +// Retrieve looks for an object in cache and returns it (or an error if not found) +func (c *BoltDBCache) Retrieve(cacheKey string) (string, error) { + + level.Debug(c.T.Logger).Log("event", "boltdb cache retrieve", "key", cacheKey) + + _, dataKey := c.getKeyNames(cacheKey) + + c.checkExpiration(cacheKey) + + return c.retrieve(dataKey) +} + +// retrieve looks for an object in cache and returns it (or an error if not found) +func (c *BoltDBCache) retrieve(cacheKey string) (string, error) { + + content := "" + + err := c.dbh.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(c.Config.Bucket)) + v := b.Get([]byte(cacheKey)) + if v == nil { + level.Debug(c.T.Logger).Log("event", "boltdb cache miss", "key", cacheKey) + return fmt.Errorf("Value for key [%s] not in cache", cacheKey) + } + content = string(v) + return nil + }) + if err != nil { + return "", err + } + + return content, nil +} + +// checkExpiration verifies that a cacheKey is not expired +func (c *BoltDBCache) checkExpiration(cacheKey string) { + + expKey, _ := c.getKeyNames(cacheKey) + + content, err := c.retrieve(expKey) + if err == nil { + // We found this key, let's see if it's expired + expiration, err := strconv.ParseInt(string(content), 10, 64) + if err != nil || expiration < time.Now().Unix() { + c.Delete(cacheKey) + } + } +} + +// Delete removes an object in cache, if present +func (c *BoltDBCache) Delete(cacheKey string) error { + + level.Debug(c.T.Logger).Log("event", "boltdb cache delete", "key", cacheKey) + + expKey, dataKey := c.getKeyNames(cacheKey) + + return c.dbh.Update(func(tx *bolt.Tx) error { + + b := tx.Bucket([]byte(c.Config.Bucket)) + + err1 := b.Delete([]byte(expKey)) + if err1 != nil { + level.Error(c.T.Logger).Log("event", "boltdb cache key delete failure", "key", expKey, "reason", err1.Error()) + } + + err2 := b.Delete([]byte(dataKey)) + if err2 != nil { + level.Error(c.T.Logger).Log("event", "boltdb cache key delete failure", "key", dataKey, "reason", err2.Error()) + } + + c.T.ChannelCreateMtx.Lock() + + // Close out the channel if it exists + if _, ok := c.T.ResponseChannels[cacheKey]; ok { + close(c.T.ResponseChannels[cacheKey]) + delete(c.T.ResponseChannels, cacheKey) + } + + // Unlock + c.T.ChannelCreateMtx.Unlock() + + if err1 != nil { + return err1 + } + if err2 != nil { + return err2 + } + + return nil + + }) + +} + +// Reap continually iterates through the cache to find expired elements and removes them +func (c *BoltDBCache) Reap() { + + for { + c.ReapOnce() + time.Sleep(time.Duration(c.T.Config.Caching.ReapSleepMS) * time.Millisecond) + } + +} + +func (c *BoltDBCache) ReapOnce() { + + now := time.Now().Unix() + expiredKeys := make([]string, 0) + + // Iterate through the cache to find any expiration keys and check their value + c.dbh.View(func(tx *bolt.Tx) error { + // Assume bucket exists and has keys + b := tx.Bucket([]byte(c.Config.Bucket)) + cursor := b.Cursor() + + for k, v := cursor.First(); k != nil; k, v = cursor.Next() { + + expKey := string(k) + + if strings.HasSuffix(expKey, ".expiration") { + + expiration, err := strconv.ParseInt(string(v), 10, 64) + if err != nil || expiration < now { + + expiredKeys = append(expiredKeys, strings.Replace(expKey, ".expiration", "", -1)) + + } + } + } + + return nil + }) + + // Iterate through the expired keys so we can delete them + for _, cacheKey := range expiredKeys { + c.Delete(cacheKey) + } + +} + +// Close closes the BoltDBCache +func (c *BoltDBCache) Close() error { + return c.dbh.Close() +} + +func (c *BoltDBCache) getKeyNames(cacheKey string) (string, string) { + return cacheKey + ".expiration", cacheKey + ".data" +} diff --git a/boltdb_test.go b/boltdb_test.go new file mode 100644 index 000000000..ff591e81a --- /dev/null +++ b/boltdb_test.go @@ -0,0 +1,104 @@ +/** +* Copyright 2018 Comcast Cable Communications Management, LLC +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://www.apache.org/licenses/LICENSE-2.0 +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. + */ + +package main + +import ( + "testing" + + "github.com/go-kit/kit/log" +) + +func TestBoltDBCache_Connect(t *testing.T) { + cfg := Config{Caching: CachingConfig{ReapSleepMS: 1}} + tr := TricksterHandler{Logger: log.NewNopLogger(), Config: &cfg} + bc := BoltDBCache{T: &tr, Config: BoltDBCacheConfig{Filename: "/tmp/test.db", Bucket: "trickster_test"}} + + // it should connect + err := bc.Connect() + if err != nil { + t.Error(err) + } + + bc.Close() + +} + +func TestBoltDBCache_Store(t *testing.T) { + cfg := Config{Caching: CachingConfig{ReapSleepMS: 1}} + tr := TricksterHandler{Logger: log.NewNopLogger(), Config: &cfg} + bc := BoltDBCache{T: &tr, Config: BoltDBCacheConfig{Filename: "/tmp/test.db", Bucket: "trickster_test"}} + + err := bc.Connect() + if err != nil { + t.Error(err) + } + defer bc.Close() + + // it should store a value + err = bc.Store("cacheKey", "data", 60000) + if err != nil { + t.Error(err) + } +} + +func TestBoltDBCache_Delete(t *testing.T) { + cfg := Config{Caching: CachingConfig{ReapSleepMS: 1}} + tr := TricksterHandler{Logger: log.NewNopLogger(), Config: &cfg} + bc := BoltDBCache{T: &tr, Config: BoltDBCacheConfig{Filename: "/tmp/test.db", Bucket: "trickster_test"}} + + err := bc.Connect() + if err != nil { + t.Error(err) + } + defer bc.Close() + + // it should store a value + err = bc.Store("cacheKey", "data", 60000) + if err != nil { + t.Error(err) + } + + // it should store a value + err = bc.Delete("cacheKey") + if err != nil { + t.Error(err) + } + +} + +func TestBoltDBCache_Retrieve(t *testing.T) { + cfg := Config{Caching: CachingConfig{ReapSleepMS: 1}} + tr := TricksterHandler{Logger: log.NewNopLogger(), Config: &cfg} + bc := BoltDBCache{T: &tr, Config: BoltDBCacheConfig{Filename: "/tmp/test.db", Bucket: "trickster_test"}} + + err := bc.Connect() + if err != nil { + t.Error(err) + } + defer bc.Close() + + err = bc.Store("cacheKey", "data", 60000) + if err != nil { + t.Error(err) + } + + // it should retrieve a value + data, err := bc.Retrieve("cacheKey") + if err != nil { + t.Error(err) + } + if data != "data" { + t.Errorf("wanted \"%s\". got \"%s\".", "data", data) + } +} diff --git a/cache.go b/cache.go index 0123c67ef..ab466088c 100644 --- a/cache.go +++ b/cache.go @@ -18,6 +18,7 @@ const ( ctMemory = "memory" ctFilesystem = "filesystem" ctRedis = "redis" + ctBoltDB = "boltdb" ) // Cache is the interface for the supported caching fabrics @@ -34,6 +35,8 @@ func getCache(t *TricksterHandler) Cache { switch t.Config.Caching.CacheType { case ctFilesystem: return &FilesystemCache{Config: t.Config.Caching.Filesystem, T: t} + case ctBoltDB: + return &BoltDBCache{Config: t.Config.Caching.BoltDB, T: t} case ctRedis: return &RedisCache{Config: t.Config.Caching.Redis, T: t} default: diff --git a/conf/example.conf b/conf/example.conf index 222daecbd..a8cf6565f 100644 --- a/conf/example.conf +++ b/conf/example.conf @@ -40,6 +40,17 @@ cache_type = 'memory' # default is '/tmp/trickster' # cache_path = '/tmp/trickster' + # Configuration options when using a BoltDb Cache + #[cache.boltdb] + + # filename defines the fully-qualified filename where the Trickster cache will be maintained + # default is '/tmp/trickster/trickster.db' + # filename = '/tmp/trickster/trickster.db' + + # bucket defines the name of the BotlDb bucket (similar to a namespace) under which our key value store lives + # default is 'trickster' + # bucket = 'trickster' + # Configuration options for mapping Origin(s) [origins] ### The default origin diff --git a/config.go b/config.go index 3ecfe6c38..7de4407ec 100644 --- a/config.go +++ b/config.go @@ -50,20 +50,29 @@ type CachingConfig struct { // CacheType represents the type of cache that we wish to use: "memory", "filesystem", or "redis" CacheType string `toml:"cache_type"` RecordTTLSecs int64 `toml:"record_ttl_secs"` - Redis RedisConfig `toml:"redis"` + Redis RedisCacheConfig `toml:"redis"` Filesystem FilesystemCacheConfig `toml:"filesystem"` ReapSleepMS int64 `toml:"reap_sleep_ms"` Compression bool `toml:"compression"` + BoltDB BoltDBCacheConfig `toml:"boltdb"` } -// RedisConfig is a collection of Configurations for Connecting to Redis -type RedisConfig struct { +// RedisCacheConfig is a collection of Configurations for Connecting to Redis +type RedisCacheConfig struct { // Protocol represents the connection method (e.g., "tcp", "unix", etc.) Protocol string `toml:"protocol"` // Endpoint represents FQDN:port or IPAddress:Port of the Redis server Endpoint string `toml:"endpoint"` } +// BoltDBCacheConfig is a collection of Configurations for storing cached data on the Filesystem +type BoltDBCacheConfig struct { + // Filename represents the filename (including path) of the BotlDB database + Filename string `toml:"filename"` + // Bucket represents the name of the bucket within BoltDB under which Trickster's keys will be stored. + Bucket string `toml:"bucket"` +} + // FilesystemCacheConfig is a collection of Configurations for storing cached data on the Filesystem type FilesystemCacheConfig struct { // CachePath represents the path on disk where our cache will live @@ -98,14 +107,21 @@ type LoggingConfig struct { // NewConfig returns a Config initialized with default values. func NewConfig() *Config { + + defaultCachePath := "/tmp/trickster" + return &Config{ Caching: CachingConfig{ + CacheType: ctMemory, RecordTTLSecs: 21600, - Redis: RedisConfig{Protocol: "tcp", Endpoint: "redis:6379"}, - Filesystem: FilesystemCacheConfig{CachePath: "/tmp/trickster"}, - ReapSleepMS: 1000, - Compression: true, + + Redis: RedisCacheConfig{Protocol: "tcp", Endpoint: "redis:6379"}, + Filesystem: FilesystemCacheConfig{CachePath: defaultCachePath}, + BoltDB: BoltDBCacheConfig{Filename: defaultCachePath + "/trickster.db", Bucket: "trickster"}, + + ReapSleepMS: 1000, + Compression: true, }, Logging: LoggingConfig{ LogFile: "", diff --git a/docs/caches.md b/docs/caches.md index a069c14f3..e20c5dc0f 100644 --- a/docs/caches.md +++ b/docs/caches.md @@ -22,6 +22,9 @@ The Filesystem Cache is a popular option when you have larger dashboard setup (e The default Filesystem Cache path is `/tmp/trickster`. The sample configuration demonstrates how to specify a custom cache path. Ensure that the user account running Trickster has read/write access to the custom directory or the application will exit on startup upon testing filesystem access. All users generally have access to /tmp so there is no concern about permissions in the default case. +## BoltDB Cache + +The BoltDB Cache is a popular key/value store, created by [Ben Johnson](https://github.com/benbjohnson). [CoreOS's bbolt fork](https://github.com/coreos/bbolt) is the version implemented in Trickster. A BoltDB store is a filesystem-based solution that stores the entire database in a single file. Trickster, by default, creates the database at `/tmp/trickster/trickster.db` and uses a bucket name of 'trickster' for storing key/value data. See the example config file for details on customizing this aspect of your Trickster deployment. The same guidance about filesystem permissions described in the Filesystem Cache section above apply to a BoltDB Cache. ## Redis Cache @@ -29,6 +32,7 @@ Redis is a good option for larger dashboard setups that also have heavy user tra Ensure that your Redis instance is located close to your Trickster instance in order to minimize additional roundtrip latency. + ## Purging the Cache Cache purges should not be necessary, but in the event that you wish to do so, the following steps should be followed based upon your selected Cache Type. diff --git a/filesystem.go b/filesystem.go index 802ffdfc3..68705490a 100644 --- a/filesystem.go +++ b/filesystem.go @@ -38,7 +38,7 @@ type FilesystemCache struct { func (c *FilesystemCache) Connect() error { level.Info(c.T.Logger).Log("event", "filesystem cache setup", "cachePath", c.Config.CachePath) - if err := mustMakeDirectory(c.Config.CachePath); err != nil { + if err := makeDirectory(c.Config.CachePath); err != nil { return err } @@ -157,11 +157,11 @@ func writeable(path string) bool { return unix.Access(path, unix.W_OK) == nil } -// mustMakeDirectory creates a directory on the filesystem and exits the application in the event of a failure. -func mustMakeDirectory(path string) error { +// makeDirectory creates a directory on the filesystem and exits the application in the event of a failure. +func makeDirectory(path string) error { err := os.MkdirAll(path, 0755) if err != nil || !writeable(path) { - return fmt.Errorf(`[%s] directory is not writeable by the trickster`, path) + return fmt.Errorf(`[%s] directory is not writeable by the trickster: %s`, path, err.Error()) } return nil diff --git a/redis.go b/redis.go index 277804592..f20e29db3 100644 --- a/redis.go +++ b/redis.go @@ -24,7 +24,7 @@ import ( // RedisCache represents a redis cache object that conforms to the Cache interface type RedisCache struct { T *TricksterHandler - Config RedisConfig + Config RedisCacheConfig client *redis.Client CacheKeys sync.Map } diff --git a/redis_test.go b/redis_test.go index 72670c200..a076ab6d2 100644 --- a/redis_test.go +++ b/redis_test.go @@ -29,7 +29,7 @@ func setupRedisCache() (RedisCache, func()) { Logger: log.NewNopLogger(), ResponseChannels: make(map[string]chan *ClientRequestContext), } - rcfg := RedisConfig{Endpoint: s.Addr()} + rcfg := RedisCacheConfig{Endpoint: s.Addr()} close := func() { s.Close() }