diff --git a/metricbeat/module/redis/info/data.go b/metricbeat/module/redis/info/data.go index 6ee9de991e0a..1a6799bfe68f 100644 --- a/metricbeat/module/redis/info/data.go +++ b/metricbeat/module/redis/info/data.go @@ -18,9 +18,9 @@ package info import ( - "github.com/elastic/beats/libbeat/common" s "github.com/elastic/beats/libbeat/common/schema" c "github.com/elastic/beats/libbeat/common/schema/mapstrstr" + "github.com/elastic/beats/metricbeat/mb" ) var ( @@ -237,12 +237,14 @@ var ( ) // Map data to MapStr -func eventMapping(info map[string]string) common.MapStr { +func eventMapping(r mb.ReporterV2, info map[string]string) { // Full mapping from info source := map[string]interface{}{} for key, val := range info { source[key] = val } data, _ := schema.Apply(source) - return data + r.Event(mb.Event{ + MetricSetFields: data, + }) } diff --git a/metricbeat/module/redis/info/info.go b/metricbeat/module/redis/info/info.go index a1cec8a655de..57f0fa86c45e 100644 --- a/metricbeat/module/redis/info/info.go +++ b/metricbeat/module/redis/info/info.go @@ -19,15 +19,13 @@ package info import ( "strconv" - "time" - "github.com/elastic/beats/libbeat/common" + "github.com/pkg/errors" + "github.com/elastic/beats/libbeat/logp" "github.com/elastic/beats/metricbeat/mb" "github.com/elastic/beats/metricbeat/mb/parse" "github.com/elastic/beats/metricbeat/module/redis" - - rd "github.com/garyburd/redigo/redis" ) var ( @@ -43,41 +41,25 @@ func init() { // MetricSet for fetching Redis server information and statistics. type MetricSet struct { - mb.BaseMetricSet - pool *rd.Pool + *redis.MetricSet } // New creates new instance of MetricSet func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - // Unpack additional configuration options. - config := struct { - IdleTimeout time.Duration `config:"idle_timeout"` - Network string `config:"network"` - MaxConn int `config:"maxconn" validate:"min=1"` - Password string `config:"password"` - }{ - Network: "tcp", - MaxConn: 10, - Password: "", - } - err := base.Module().UnpackConfig(&config) + ms, err := redis.NewMetricSet(base) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to create 'info' metricset") } - - return &MetricSet{ - BaseMetricSet: base, - pool: redis.CreatePool(base.Host(), config.Password, config.Network, - config.MaxConn, config.IdleTimeout, base.Module().Config().Timeout), - }, nil + return &MetricSet{ms}, nil } // Fetch fetches metrics from Redis by issuing the INFO command. -func (m *MetricSet) Fetch() (common.MapStr, error) { +func (m *MetricSet) Fetch(r mb.ReporterV2) { // Fetch default INFO. - info, err := redis.FetchRedisInfo("default", m.pool.Get()) + info, err := redis.FetchRedisInfo("default", m.Connection()) if err != nil { - return nil, err + logp.Err("Failed to fetch redis info: %s", err) + return } // In 5.0 some fields are renamed, maintain both names, old ones will be deprecated @@ -95,12 +77,13 @@ func (m *MetricSet) Fetch() (common.MapStr, error) { } } - slowLogLength, err := redis.FetchSlowLogLength(m.pool.Get()) + slowLogLength, err := redis.FetchSlowLogLength(m.Connection()) if err != nil { - return nil, err + logp.Err("Failed to fetch slow log length: %s", err) + return } info["slowlog_len"] = strconv.FormatInt(slowLogLength, 10) debugf("Redis INFO from %s: %+v", m.Host(), info) - return eventMapping(info), nil + eventMapping(r, info) } diff --git a/metricbeat/module/redis/info/info_integration_test.go b/metricbeat/module/redis/info/info_integration_test.go index bbcfd1b8601c..f723bc372e43 100644 --- a/metricbeat/module/redis/info/info_integration_test.go +++ b/metricbeat/module/redis/info/info_integration_test.go @@ -27,26 +27,22 @@ import ( mbtest "github.com/elastic/beats/metricbeat/mb/testing" "github.com/elastic/beats/metricbeat/module/redis" - rd "github.com/garyburd/redigo/redis" "github.com/stretchr/testify/assert" ) -const ( - password = "foobared" -) - var redisHost = redis.GetRedisEnvHost() + ":" + redis.GetRedisEnvPort() func TestFetch(t *testing.T) { compose.EnsureUp(t, "redis") - f := mbtest.NewEventFetcher(t, getConfig("")) - event, err := f.Fetch() + ms := mbtest.NewReportingMetricSetV2(t, getConfig()) + events, err := mbtest.ReportingFetchV2(ms) if err != nil { t.Fatal("fetch", err) } + event := events[0].MetricSetFields - t.Logf("%s/%s event: %+v", f.Module().Name(), f.Name(), event) + t.Logf("%s/%s event: %+v", ms.Module().Name(), ms.Name(), event) // Check fields assert.Equal(t, 9, len(event)) @@ -57,96 +53,17 @@ func TestFetch(t *testing.T) { func TestData(t *testing.T) { compose.EnsureUp(t, "redis") - f := mbtest.NewEventFetcher(t, getConfig("")) - - err := mbtest.WriteEvent(f, t) + ms := mbtest.NewReportingMetricSetV2(t, getConfig()) + err := mbtest.WriteEventsReporterV2(ms, t, "") if err != nil { t.Fatal("write", err) } } -func TestPasswords(t *testing.T) { - compose.EnsureUp(t, "redis") - - // Add password and ensure it gets reset - defer func() { - err := resetPassword(redisHost, password) - if err != nil { - t.Fatal("resetting password", err) - } - }() - - err := addPassword(redisHost, password) - if err != nil { - t.Fatal("adding password", err) - } - - // Test Fetch metrics with missing password - f := mbtest.NewEventFetcher(t, getConfig("")) - _, err = f.Fetch() - if assert.Error(t, err, "missing password") { - assert.Contains(t, err, "NOAUTH Authentication required.") - } - - // Config redis and metricset with an invalid password - f = mbtest.NewEventFetcher(t, getConfig("blah")) - _, err = f.Fetch() - if assert.Error(t, err, "invalid password") { - assert.Contains(t, err, "ERR invalid password") - } - - // Config redis and metricset with a valid password - f = mbtest.NewEventFetcher(t, getConfig(password)) - _, err = f.Fetch() - assert.NoError(t, err, "valid password") -} - -// addPassword will add a password to redis. -func addPassword(host, pass string) error { - c, err := rd.Dial("tcp", host) - if err != nil { - return err - } - defer c.Close() - - _, err = c.Do("CONFIG", "SET", "requirepass", pass) - return err -} - -// resetPassword changes the password to the redis DB. -func resetPassword(host, currentPass string) error { - c, err := rd.Dial("tcp", host) - if err != nil { - return err - } - defer c.Close() - - _, err = c.Do("AUTH", currentPass) - if err != nil { - return err - } - - _, err = c.Do("CONFIG", "SET", "requirepass", "") - return err -} - -// writeToRedis will write to the default DB 0. -func writeToRedis(host string) error { - c, err := rd.Dial("tcp", host) - if err != nil { - return err - } - defer c.Close() - - _, err = c.Do("SET", "foo", "bar") - return err -} - -func getConfig(password string) map[string]interface{} { +func getConfig() map[string]interface{} { return map[string]interface{}{ "module": "redis", "metricsets": []string{"info"}, "hosts": []string{redisHost}, - "password": password, } } diff --git a/metricbeat/module/redis/key/data.go b/metricbeat/module/redis/key/data.go index 65061b83c0ff..076a5bce3490 100644 --- a/metricbeat/module/redis/key/data.go +++ b/metricbeat/module/redis/key/data.go @@ -24,14 +24,14 @@ import ( "github.com/elastic/beats/metricbeat/mb" ) -func eventMapping(r mb.ReporterV2, keyspace uint, info map[string]interface{}) { +func eventMapping(keyspace uint, info map[string]interface{}) mb.Event { info["id"] = fmt.Sprintf("%d:%s", keyspace, info["name"]) - r.Event(mb.Event{ + return mb.Event{ MetricSetFields: info, ModuleFields: common.MapStr{ "keyspace": common.MapStr{ "id": fmt.Sprintf("db%d", keyspace), }, }, - }) + } } diff --git a/metricbeat/module/redis/key/key.go b/metricbeat/module/redis/key/key.go index 86943729dcac..c60db25ad445 100644 --- a/metricbeat/module/redis/key/key.go +++ b/metricbeat/module/redis/key/key.go @@ -18,14 +18,12 @@ package key import ( - "time" + "github.com/pkg/errors" "github.com/elastic/beats/libbeat/logp" "github.com/elastic/beats/metricbeat/mb" "github.com/elastic/beats/metricbeat/mb/parse" "github.com/elastic/beats/metricbeat/module/redis" - - rd "github.com/garyburd/redigo/redis" ) var ( @@ -40,8 +38,7 @@ func init() { // MetricSet for fetching Redis server information and statistics. type MetricSet struct { - mb.BaseMetricSet - pool *rd.Pool + *redis.MetricSet patterns []KeyPattern } @@ -54,34 +51,28 @@ type KeyPattern struct { // New creates new instance of MetricSet func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - // Unpack additional configuration options. config := struct { - IdleTimeout time.Duration `config:"idle_timeout"` - Network string `config:"network"` - MaxConn int `config:"maxconn" validate:"min=1"` - Password string `config:"password"` - Patterns []KeyPattern `config:"key.patterns" validate:"nonzero,required"` - }{ - Network: "tcp", - MaxConn: 10, - Password: "", - } + Patterns []KeyPattern `config:"key.patterns" validate:"nonzero,required"` + }{} err := base.Module().UnpackConfig(&config) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to read configuration for 'key' metricset") + } + + ms, err := redis.NewMetricSet(base) + if err != nil { + return nil, errors.Wrap(err, "failed to create 'key' metricset") } return &MetricSet{ - BaseMetricSet: base, - pool: redis.CreatePool(base.Host(), config.Password, config.Network, - config.MaxConn, config.IdleTimeout, base.Module().Config().Timeout), - patterns: config.Patterns, + MetricSet: ms, + patterns: config.Patterns, }, nil } // Fetch fetches information from Redis keys func (m *MetricSet) Fetch(r mb.ReporterV2) { - conn := m.pool.Get() + conn := m.Connection() for _, p := range m.patterns { if err := redis.Select(conn, p.Keyspace); err != nil { logp.Err("Failed to select keyspace %d: %s", p.Keyspace, err) @@ -90,7 +81,7 @@ func (m *MetricSet) Fetch(r mb.ReporterV2) { keys, err := redis.FetchKeys(conn, p.Pattern, p.Limit) if err != nil { - logp.Err("Failed to fetch list of keys in keyspace %d with pattern '%s': %s", p.Keyspace, p.Pattern, err) + logp.Err("Failed to list keys in keyspace %d with pattern '%s': %s", p.Keyspace, p.Pattern, err) continue } if p.Limit > 0 && len(keys) > int(p.Limit) { @@ -104,12 +95,11 @@ func (m *MetricSet) Fetch(r mb.ReporterV2) { logp.Err("Failed to fetch key info for key %s in keyspace %d", key, p.Keyspace) continue } - eventMapping(r, p.Keyspace, keyInfo) + event := eventMapping(p.Keyspace, keyInfo) + if !r.Event(event) { + debugf("Failed to report event, interrupting Fetch") + return + } } } } - -// Close connections -func (m *MetricSet) Close() error { - return m.pool.Close() -} diff --git a/metricbeat/module/redis/keyspace/data.go b/metricbeat/module/redis/keyspace/data.go index 15eff5b06ecc..5b0bcda18799 100644 --- a/metricbeat/module/redis/keyspace/data.go +++ b/metricbeat/module/redis/keyspace/data.go @@ -23,18 +23,22 @@ import ( "github.com/elastic/beats/libbeat/common" s "github.com/elastic/beats/libbeat/common/schema" c "github.com/elastic/beats/libbeat/common/schema/mapstrstr" + "github.com/elastic/beats/metricbeat/mb" "github.com/elastic/beats/metricbeat/module/redis" ) // Map data to MapStr -func eventsMapping(info map[string]string) []common.MapStr { - events := []common.MapStr{} +func eventsMapping(r mb.ReporterV2, info map[string]string) { for key, space := range getKeyspaceStats(info) { space["id"] = key - events = append(events, space) + event := mb.Event{ + MetricSetFields: space, + } + if !r.Event(event) { + debugf("Failed to report event, interrupting Fetch") + return + } } - - return events } func getKeyspaceStats(info map[string]string) map[string]common.MapStr { diff --git a/metricbeat/module/redis/keyspace/keyspace.go b/metricbeat/module/redis/keyspace/keyspace.go index 9eb0e3a22a20..8e6354e820be 100644 --- a/metricbeat/module/redis/keyspace/keyspace.go +++ b/metricbeat/module/redis/keyspace/keyspace.go @@ -18,15 +18,12 @@ package keyspace import ( - "time" + "github.com/pkg/errors" - "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" "github.com/elastic/beats/metricbeat/mb" "github.com/elastic/beats/metricbeat/mb/parse" "github.com/elastic/beats/metricbeat/module/redis" - - rd "github.com/garyburd/redigo/redis" ) var ( @@ -42,43 +39,27 @@ func init() { // MetricSet for fetching Redis server information and statistics. type MetricSet struct { - mb.BaseMetricSet - pool *rd.Pool + *redis.MetricSet } // New creates new instance of MetricSet func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - // Unpack additional configuration options. - config := struct { - IdleTimeout time.Duration `config:"idle_timeout"` - Network string `config:"network"` - MaxConn int `config:"maxconn" validate:"min=1"` - Password string `config:"password"` - }{ - Network: "tcp", - MaxConn: 10, - Password: "", - } - err := base.Module().UnpackConfig(&config) + ms, err := redis.NewMetricSet(base) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to create 'keyspace' metricset") } - - return &MetricSet{ - BaseMetricSet: base, - pool: redis.CreatePool(base.Host(), config.Password, config.Network, - config.MaxConn, config.IdleTimeout, base.Module().Config().Timeout), - }, nil + return &MetricSet{ms}, nil } // Fetch fetches metrics from Redis by issuing the INFO command. -func (m *MetricSet) Fetch() ([]common.MapStr, error) { +func (m *MetricSet) Fetch(r mb.ReporterV2) { // Fetch default INFO. - info, err := redis.FetchRedisInfo("keyspace", m.pool.Get()) + info, err := redis.FetchRedisInfo("keyspace", m.Connection()) if err != nil { - return nil, err + logp.Err("Failed to fetch redis info for keyspaces: %s", err) + return } debugf("Redis INFO from %s: %+v", m.Host(), info) - return eventsMapping(info), nil + eventsMapping(r, info) } diff --git a/metricbeat/module/redis/keyspace/keyspace_integration_test.go b/metricbeat/module/redis/keyspace/keyspace_integration_test.go index 35710d174e20..03b4d296b05c 100644 --- a/metricbeat/module/redis/keyspace/keyspace_integration_test.go +++ b/metricbeat/module/redis/keyspace/keyspace_integration_test.go @@ -39,18 +39,18 @@ func TestFetch(t *testing.T) { addEntry(t) // Fetch data - f := mbtest.NewEventsFetcher(t, getConfig()) - events, err := f.Fetch() + ms := mbtest.NewReportingMetricSetV2(t, getConfig()) + events, err := mbtest.ReportingFetchV2(ms) if err != nil { t.Fatal("fetch", err) } - t.Logf("%s/%s event: %+v", f.Module().Name(), f.Name(), events) + t.Logf("%s/%s event: %+v", ms.Module().Name(), ms.Name(), events) // Make sure at least 1 db keyspace exists assert.True(t, len(events) > 0) - keyspace := events[0] + keyspace := events[0].MetricSetFields assert.True(t, keyspace["avg_ttl"].(int64) >= 0) assert.True(t, keyspace["expires"].(int64) >= 0) @@ -63,9 +63,8 @@ func TestData(t *testing.T) { addEntry(t) - f := mbtest.NewEventsFetcher(t, getConfig()) - - err := mbtest.WriteEvents(f, t) + ms := mbtest.NewReportingMetricSetV2(t, getConfig()) + err := mbtest.WriteEventsReporterV2(ms, t, "") if err != nil { t.Fatal("write", err) } diff --git a/metricbeat/module/redis/metricset.go b/metricbeat/module/redis/metricset.go new file mode 100644 index 000000000000..8a2e53aab2d1 --- /dev/null +++ b/metricbeat/module/redis/metricset.go @@ -0,0 +1,68 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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 redis + +import ( + "time" + + rd "github.com/garyburd/redigo/redis" + "github.com/pkg/errors" + + "github.com/elastic/beats/metricbeat/mb" +) + +// MetricSet for fetching Redis server information and statistics. +type MetricSet struct { + mb.BaseMetricSet + pool *rd.Pool +} + +// NewMetricSet creates the base for Redis metricsets +func NewMetricSet(base mb.BaseMetricSet) (*MetricSet, error) { + // Unpack additional configuration options. + config := struct { + IdleTimeout time.Duration `config:"idle_timeout"` + Network string `config:"network"` + MaxConn int `config:"maxconn" validate:"min=1"` + Password string `config:"password"` + }{ + Network: "tcp", + MaxConn: 10, + Password: "", + } + err := base.Module().UnpackConfig(&config) + if err != nil { + return nil, errors.Wrap(err, "failed to read configuration") + } + + return &MetricSet{ + BaseMetricSet: base, + pool: CreatePool(base.Host(), config.Password, config.Network, + config.MaxConn, config.IdleTimeout, base.Module().Config().Timeout), + }, nil +} + +// Connection returns a redis connection from the pool +func (m *MetricSet) Connection() rd.Conn { + return m.pool.Get() +} + +// Close redis connections +func (m *MetricSet) Close() error { + return m.pool.Close() +} diff --git a/metricbeat/module/redis/metricset_integration_test.go b/metricbeat/module/redis/metricset_integration_test.go new file mode 100644 index 000000000000..4c21051ecce9 --- /dev/null +++ b/metricbeat/module/redis/metricset_integration_test.go @@ -0,0 +1,149 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you 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. + +// +build integration + +package redis + +import ( + "testing" + + rd "github.com/garyburd/redigo/redis" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/tests/compose" + "github.com/elastic/beats/metricbeat/mb" + "github.com/elastic/beats/metricbeat/mb/parse" +) + +const ( + password = "foobared" +) + +func TestPasswords(t *testing.T) { + compose.EnsureUp(t, "redis") + + registry := mb.NewRegister() + err := registry.AddModule("redis", mb.DefaultModuleFactory) + require.NoError(t, err) + + registry.MustAddMetricSet("redis", "test", newDummyMetricSet, + mb.WithHostParser(parse.PassThruHostParser), + ) + + // Add password and ensure it gets reset + defer func() { + err := resetPassword(host, password) + if err != nil { + t.Fatal("resetting password", err) + } + }() + + err = addPassword(host, password) + if err != nil { + t.Fatal("adding password", err) + } + + // Test Fetch metrics with missing password + ms := getMetricSet(t, registry, getConfig("")) + _, err = ms.Connection().Do("PING") + if assert.Error(t, err, "missing password") { + assert.Contains(t, err, "NOAUTH Authentication required.") + } + + // Config redis and metricset with an invalid password + ms = getMetricSet(t, registry, getConfig("blah")) + _, err = ms.Connection().Do("PING") + if assert.Error(t, err, "invalid password") { + assert.Contains(t, err, "ERR invalid password") + } + + // Config redis and metricset with a valid password + ms = getMetricSet(t, registry, getConfig(password)) + _, err = ms.Connection().Do("PING") + assert.Empty(t, err, "valid password") +} + +// addPassword will add a password to redis. +func addPassword(host, pass string) error { + c, err := rd.Dial("tcp", host) + if err != nil { + return err + } + defer c.Close() + + _, err = c.Do("CONFIG", "SET", "requirepass", pass) + return err +} + +// resetPassword changes the password to the redis DB. +func resetPassword(host, currentPass string) error { + c, err := rd.Dial("tcp", host) + if err != nil { + return err + } + defer c.Close() + + _, err = c.Do("AUTH", currentPass) + if err != nil { + return err + } + + _, err = c.Do("CONFIG", "SET", "requirepass", "") + return err +} + +// dummyMetricSet is a metricset used only to instantiate a metricset +// from config using a registry +type dummyMetricSet struct { + *MetricSet +} + +func newDummyMetricSet(base mb.BaseMetricSet) (mb.MetricSet, error) { + ms, err := NewMetricSet(base) + return &dummyMetricSet{ms}, err +} + +func (m *dummyMetricSet) Fetch(r mb.ReporterV2) { +} + +func getMetricSet(t *testing.T, registry *mb.Register, config map[string]interface{}) *MetricSet { + t.Helper() + + c, err := common.NewConfigFrom(config) + require.NoError(t, err) + + _, metricsets, err := mb.NewModule(c, registry) + require.NoError(t, err) + require.Len(t, metricsets, 1) + + ms, ok := metricsets[0].(*dummyMetricSet) + require.True(t, ok, "metricset must be dummyMetricSet") + + return ms.MetricSet +} + +func getConfig(password string) map[string]interface{} { + return map[string]interface{}{ + "module": "redis", + "metricsets": "test", + "hosts": []string{host}, + "password": password, + } +} diff --git a/metricbeat/module/redis/redis.go b/metricbeat/module/redis/redis.go index 06cb76193ec5..9c039b6bf8d1 100644 --- a/metricbeat/module/redis/redis.go +++ b/metricbeat/module/redis/redis.go @@ -65,8 +65,6 @@ func ParseRedisLine(s string, delimiter string) []string { // FetchRedisInfo returns a map of requested stats. func FetchRedisInfo(stat string, c rd.Conn) (map[string]string, error) { - defer c.Close() - out, err := rd.String(c.Do("INFO", stat)) if err != nil { logp.Err("Error retrieving INFO stats: %v", err) diff --git a/metricbeat/module/redis/redis_integration_test.go b/metricbeat/module/redis/redis_integration_test.go index dca51fb8f415..933a5ce6ee72 100644 --- a/metricbeat/module/redis/redis_integration_test.go +++ b/metricbeat/module/redis/redis_integration_test.go @@ -20,6 +20,7 @@ package redis import ( + "strings" "testing" rd "github.com/garyburd/redigo/redis" @@ -32,6 +33,41 @@ import ( var host = GetRedisEnvHost() + ":" + GetRedisEnvPort() +func TestFetchRedisInfo(t *testing.T) { + compose.EnsureUp(t, "redis") + + conn, err := rd.Dial("tcp", host) + if err != nil { + t.Fatal("connect", err) + } + defer conn.Close() + + t.Run("default info", func(t *testing.T) { + info, err := FetchRedisInfo("default", conn) + require.NoError(t, err) + + _, ok := info["redis_version"] + assert.True(t, ok, "redis_version should be in redis info") + }) + + t.Run("keyspace info", func(t *testing.T) { + conn.Do("FLUSHALL") + conn.Do("SET", "foo", "bar") + + info, err := FetchRedisInfo("keyspace", conn) + require.NoError(t, err) + + dbFound := false + for k := range info { + if strings.HasPrefix(k, "db") { + dbFound = true + break + } + } + assert.True(t, dbFound, "there should be keyspaces in redis info") + }) +} + func TestFetchKeys(t *testing.T) { compose.EnsureUp(t, "redis")