diff --git a/api/v1alpha1/sentinel_types.go b/api/v1alpha1/sentinel_types.go index c14279a1..5f6cd4b7 100644 --- a/api/v1alpha1/sentinel_types.go +++ b/api/v1alpha1/sentinel_types.go @@ -250,6 +250,9 @@ type RedisServerDetails struct { // +operator-sdk:csv:customresourcedefinitions:type=status // +optional Config map[string]string `json:"config,omitempty"` + // +operator-sdk:csv:customresourcedefinitions:type=status + // +optional + Info map[string]string `json:"info,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index fc0018fa..03bb5fc4 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -2156,6 +2156,13 @@ func (in *RedisServerDetails) DeepCopyInto(out *RedisServerDetails) { (*out)[key] = val } } + if in.Info != nil { + in, out := &in.Info, &out.Info + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisServerDetails. diff --git a/controllers/sentinel_controller.go b/controllers/sentinel_controller.go index 64a5d60c..b98a1f12 100644 --- a/controllers/sentinel_controller.go +++ b/controllers/sentinel_controller.go @@ -161,7 +161,8 @@ func (r *SentinelReconciler) reconcileStatus(ctx context.Context, instance *saas } // redis shards info to the status - merr := cluster.SentinelDiscover(ctx, sharded.SlaveReadOnlyDiscoveryOpt, sharded.SaveConfigDiscoveryOpt) + merr := cluster.SentinelDiscover(ctx, sharded.SlaveReadOnlyDiscoveryOpt, + sharded.SaveConfigDiscoveryOpt, sharded.SlavePriorityDiscoveryOpt, sharded.ReplicationInfoDiscoveryOpt) // if the failure occurred calling sentinel discard the result and return error // otherwise keep going on and use the information that was returned, even if there were some // other errors @@ -194,6 +195,7 @@ func (r *SentinelReconciler) reconcileStatus(ctx context.Context, instance *saas Role: srv.Role, Address: srv.ID(), Config: srv.Config, + Info: srv.Info, } } } diff --git a/pkg/redis/client/fake_client.go b/pkg/redis/client/fake_client.go index a346dae2..14416123 100644 --- a/pkg/redis/client/fake_client.go +++ b/pkg/redis/client/fake_client.go @@ -150,6 +150,11 @@ func (fc *FakeClient) RedisSet(ctx context.Context, key string, value interface{ return rsp.InjectError() } +func (fc *FakeClient) RedisInfo(ctx context.Context, section string) (string, error) { + rsp := fc.pop() + return rsp.InjectResponse().(string), rsp.InjectError() +} + func (fc *FakeClient) pop() (fakeRsp FakeResponse) { fakeRsp, fc.Responses = fc.Responses[0], fc.Responses[1:] return fakeRsp diff --git a/pkg/redis/client/goredis_client.go b/pkg/redis/client/goredis_client.go index bc51eb24..aaf34dc8 100644 --- a/pkg/redis/client/goredis_client.go +++ b/pkg/redis/client/goredis_client.go @@ -165,3 +165,8 @@ func (c *GoRedisClient) RedisSet(ctx context.Context, key string, value interfac _, err := c.redis.Set(ctx, key, value, 0).Result() return err } + +func (c *GoRedisClient) RedisInfo(ctx context.Context, section string) (string, error) { + val, err := c.redis.Info(ctx, section).Result() + return val, err +} diff --git a/pkg/redis/client/interface.go b/pkg/redis/client/interface.go index 41805e57..ec3895d8 100644 --- a/pkg/redis/client/interface.go +++ b/pkg/redis/client/interface.go @@ -29,6 +29,7 @@ type TestableInterface interface { RedisBGSave(context.Context) error RedisLastSave(context.Context) (int64, error) RedisSet(context.Context, string, interface{}) error + RedisInfo(ctx context.Context, section string) (string, error) Close() error } diff --git a/pkg/redis/server/server.go b/pkg/redis/server/server.go index 5cd07eab..6c6d691b 100644 --- a/pkg/redis/server/server.go +++ b/pkg/redis/server/server.go @@ -235,6 +235,14 @@ func (srv *Server) RedisSet(ctx context.Context, key string, value interface{}) return srv.client.RedisSet(ctx, key, value) } +func (srv *Server) RedisInfo(ctx context.Context, section string) (map[string]string, error) { + val, err := srv.client.RedisInfo(ctx, section) + if err != nil { + return nil, err + } + return InfoStringToMap(val), nil +} + // This is a horrible function to parse the horrible structs that the go-redis // client returns for administrative commands. I swear it's not my fault ... func sliceCmdToStruct(in interface{}, out interface{}) error { diff --git a/pkg/redis/sharded/discover.go b/pkg/redis/sharded/discover.go index b6ff5814..02fc2162 100644 --- a/pkg/redis/sharded/discover.go +++ b/pkg/redis/sharded/discover.go @@ -16,6 +16,8 @@ const ( SlaveReadOnlyDiscoveryOpt DiscoveryOption = iota SaveConfigDiscoveryOpt OnlyMasterDiscoveryOpt + SlavePriorityDiscoveryOpt + ReplicationInfoDiscoveryOpt ) func (set DiscoveryOptionSet) Has(opt DiscoveryOption) bool { @@ -64,6 +66,39 @@ func (srv *RedisServer) Discover(ctx context.Context, opts ...DiscoveryOption) e srv.Config["slave-read-only"] = slaveReadOnly } + if DiscoveryOptionSet(opts).Has(SlavePriorityDiscoveryOpt) { + slavePriority, err := srv.RedisConfigGet(ctx, "slave-priority") + if err != nil { + logger.Error(err, fmt.Sprintf("unable to get %s|%s|%s 'slave-priority' option", srv.GetAlias(), srv.Role, srv.ID())) + return err + } + srv.Config["slave-priority"] = slavePriority + } + + if DiscoveryOptionSet(opts).Has(ReplicationInfoDiscoveryOpt) && role != client.Master { + repinfo, err := srv.RedisInfo(ctx, "replication") + if err != nil { + logger.Error(err, fmt.Sprintf("unable to get %s|%s|%s replication info", srv.GetAlias(), srv.Role, srv.ID())) + return err + } + + var syncInProgress string + switch flag := repinfo["master_sync_in_progress"]; flag { + case "0": + syncInProgress = "no" + case "1": + syncInProgress = "yes" + default: + logger.Error(err, fmt.Sprintf("unexpected value '%s' for 'master_sync_in_progress' %s|%s|%s", flag, srv.GetAlias(), srv.Role, srv.ID())) + syncInProgress = "" + } + + if srv.Info == nil { + srv.Info = map[string]string{} + } + srv.Info["replication"] = fmt.Sprintf("master-link: %s, sync-in-progress: %s", repinfo["master_link_status"], syncInProgress) + } + return nil } diff --git a/pkg/redis/sharded/redis_server.go b/pkg/redis/sharded/redis_server.go index 52389677..24c8c9df 100644 --- a/pkg/redis/sharded/redis_server.go +++ b/pkg/redis/sharded/redis_server.go @@ -13,6 +13,7 @@ type RedisServer struct { *redis.Server Role client.Role Config map[string]string + Info map[string]string } func NewRedisServerFromPool(connectionString string, alias *string, pool *redis.ServerPool) (*RedisServer, error) { diff --git a/test/e2e/sentinel_suite_test.go b/test/e2e/sentinel_suite_test.go index 334a16d2..ddab085b 100644 --- a/test/e2e/sentinel_suite_test.go +++ b/test/e2e/sentinel_suite_test.go @@ -164,17 +164,19 @@ var _ = Describe("sentinel e2e suite", func() { shards[0].Status.ShardNodes.GetHostPortByPodIndex(0): { Address: shards[0].Status.ShardNodes.GetHostPortByPodIndex(0), Role: redisclient.Master, - Config: map[string]string{"save": ""}, + Config: map[string]string{"save": "", "slave-priority": "100"}, }, shards[0].Status.ShardNodes.GetHostPortByPodIndex(1): { Address: shards[0].Status.ShardNodes.GetHostPortByPodIndex(1), Role: redisclient.Slave, - Config: map[string]string{"save": "", "slave-read-only": "yes"}, + Config: map[string]string{"save": "", "slave-read-only": "yes", "slave-priority": "100"}, + Info: map[string]string{"replication": "master-link: up, sync-in-progress: no"}, }, shards[0].Status.ShardNodes.GetHostPortByPodIndex(2): { Address: shards[0].Status.ShardNodes.GetHostPortByPodIndex(2), Role: redisclient.Slave, - Config: map[string]string{"save": "", "slave-read-only": "yes"}, + Config: map[string]string{"save": "", "slave-read-only": "yes", "slave-priority": "100"}, + Info: map[string]string{"replication": "master-link: up, sync-in-progress: no"}, }, }, }, @@ -184,17 +186,19 @@ var _ = Describe("sentinel e2e suite", func() { shards[1].Status.ShardNodes.GetHostPortByPodIndex(0): { Address: shards[1].Status.ShardNodes.GetHostPortByPodIndex(0), Role: redisclient.Slave, - Config: map[string]string{"save": "", "slave-read-only": "yes"}, + Config: map[string]string{"save": "", "slave-read-only": "yes", "slave-priority": "100"}, + Info: map[string]string{"replication": "master-link: up, sync-in-progress: no"}, }, shards[1].Status.ShardNodes.GetHostPortByPodIndex(1): { Address: shards[1].Status.ShardNodes.GetHostPortByPodIndex(1), Role: redisclient.Slave, - Config: map[string]string{"save": "", "slave-read-only": "yes"}, + Config: map[string]string{"save": "", "slave-read-only": "yes", "slave-priority": "100"}, + Info: map[string]string{"replication": "master-link: up, sync-in-progress: no"}, }, shards[1].Status.ShardNodes.GetHostPortByPodIndex(2): { Address: shards[1].Status.ShardNodes.GetHostPortByPodIndex(2), Role: redisclient.Master, - Config: map[string]string{"save": ""}, + Config: map[string]string{"save": "", "slave-priority": "100"}, }, }, }, @@ -289,17 +293,19 @@ var _ = Describe("sentinel e2e suite", func() { shards[0].Status.ShardNodes.GetHostPortByPodIndex(0): { Address: shards[0].Status.ShardNodes.GetHostPortByPodIndex(0), Role: redisclient.Slave, - Config: map[string]string{"save": "", "slave-read-only": "yes"}, + Config: map[string]string{"save": "", "slave-read-only": "yes", "slave-priority": "100"}, + Info: map[string]string{"replication": "master-link: up, sync-in-progress: no"}, }, shards[0].Status.ShardNodes.GetHostPortByPodIndex(1): { Address: shards[0].Status.ShardNodes.GetHostPortByPodIndex(1), Role: redisclient.Master, - Config: map[string]string{"save": ""}, + Config: map[string]string{"save": "", "slave-priority": "100"}, }, shards[0].Status.ShardNodes.GetHostPortByPodIndex(2): { Address: shards[0].Status.ShardNodes.GetHostPortByPodIndex(2), Role: redisclient.Slave, - Config: map[string]string{"save": "", "slave-read-only": "yes"}, + Config: map[string]string{"save": "", "slave-read-only": "yes", "slave-priority": "0"}, + Info: map[string]string{"replication": "master-link: up, sync-in-progress: no"}, }, }, }, @@ -309,17 +315,19 @@ var _ = Describe("sentinel e2e suite", func() { shards[1].Status.ShardNodes.GetHostPortByPodIndex(0): { Address: shards[1].Status.ShardNodes.GetHostPortByPodIndex(0), Role: redisclient.Slave, - Config: map[string]string{"save": "", "slave-read-only": "yes"}, + Config: map[string]string{"save": "", "slave-read-only": "yes", "slave-priority": "100"}, + Info: map[string]string{"replication": "master-link: up, sync-in-progress: no"}, }, shards[1].Status.ShardNodes.GetHostPortByPodIndex(1): { Address: shards[1].Status.ShardNodes.GetHostPortByPodIndex(1), Role: redisclient.Slave, - Config: map[string]string{"save": "", "slave-read-only": "yes"}, + Config: map[string]string{"save": "", "slave-read-only": "yes", "slave-priority": "100"}, + Info: map[string]string{"replication": "master-link: up, sync-in-progress: no"}, }, shards[1].Status.ShardNodes.GetHostPortByPodIndex(2): { Address: shards[1].Status.ShardNodes.GetHostPortByPodIndex(2), Role: redisclient.Master, - Config: map[string]string{"save": ""}, + Config: map[string]string{"save": "", "slave-priority": "100"}, }, }, },