diff --git a/cmd/root.go b/cmd/root.go index beae394..8299082 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -48,7 +48,7 @@ var logger = slog.New( // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "bedel", - Short: "Small utility to sync redis acls with a master instance", + Short: "Small utility to sync redis acls with a primary instance", } // Execute adds all child commands to the root command and sets flags appropriately. diff --git a/cmd/run.go b/cmd/run.go index a456adc..5e248df 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -24,7 +24,7 @@ import ( // runCmd represents the run command var runCmd = &cobra.Command{ Use: "run", - Short: "Run the acl manager in mood loop, it will sync the follower with the master", + Short: "Run the acl manager in mood loop, it will sync the follower with the primary", Run: func(cmd *cobra.Command, args []string) { mgr := aclmanager.New(viper.GetString("address"), viper.GetString("username"), viper.GetString("password")) ctx := cmd.Context() diff --git a/cmd/runOnce.go b/cmd/runOnce.go index 3f133f8..a62ca6f 100644 --- a/cmd/runOnce.go +++ b/cmd/runOnce.go @@ -18,6 +18,8 @@ package cmd import ( "github.com/ncode/bedel/pkg/aclmanager" "github.com/spf13/viper" + "log/slog" + "os" "github.com/spf13/cobra" ) @@ -25,12 +27,30 @@ import ( // runOnceCmd represents the runOnce command var runOnceCmd = &cobra.Command{ Use: "runOnce", - Short: "Run the acl manager once, it will sync the follower with the master", + Short: "Run the acl manager once, it will sync the follower with the primary", Run: func(cmd *cobra.Command, args []string) { - mgr := aclmanager.New(viper.GetString("address"), viper.GetString("username"), viper.GetString("password")) - err := mgr.SyncAcls() + ctx := cmd.Context() + aclManager := aclmanager.New(viper.GetString("address"), viper.GetString("username"), viper.GetString("password")) + function, err := aclManager.CurrentFunction(ctx) if err != nil { - panic(err) + slog.Warn("unable to check if it's a Primary", "message", err) + os.Exit(1) + } + if function == aclmanager.Follower { + primary, err := aclManager.Primary(ctx) + if err != nil { + slog.Warn("unable to find Primary", "message", err) + os.Exit(1) + } + var added, deleted []string + added, deleted, err = aclManager.SyncAcls(ctx, primary) + if err != nil { + slog.Warn("unable to sync acls from Primary", "message", err) + os.Exit(1) + } + slog.Info("Synced acls from Primary", "added", added, "deleted", deleted) + } else { + slog.Info("Not a follower, nothing to do") } }, } diff --git a/pkg/aclmanager/aclmanager.go b/pkg/aclmanager/aclmanager.go index 993297a..476ac0a 100644 --- a/pkg/aclmanager/aclmanager.go +++ b/pkg/aclmanager/aclmanager.go @@ -9,12 +9,14 @@ import ( "log/slog" "regexp" "strings" + "sync/atomic" "time" ) const ( Primary = iota Follower + Unknown ) var ( @@ -31,6 +33,8 @@ type AclManager struct { Username string Password string RedisClient *redis.Client + primary atomic.Bool + nodes map[string]int } // New creates a new AclManager @@ -45,18 +49,23 @@ func New(addr string, username string, password string) *AclManager { Username: username, Password: password, RedisClient: redisClient, + nodes: make(map[string]int), } } -type NodeInfo struct { - Address string - Function int -} +// findNodes returns a list of nodes in the cluster based on the redis info replication command +func (a *AclManager) findNodes(ctx context.Context) (err error) { + slog.Debug("Finding nodes") + replicationInfo, err := a.RedisClient.Info(ctx, "replication").Result() + if err != nil { + return err + } -func parseRedisOutput(output string) (nodes []NodeInfo, err error) { - var masterHost, masterPort string + a.primary.Store(role.MatchString(replicationInfo)) - scanner := bufio.NewScanner(strings.NewReader(output)) + var masterHost, masterPort string + var nodes []string + scanner := bufio.NewScanner(strings.NewReader(replicationInfo)) for scanner.Scan() { line := scanner.Text() @@ -68,84 +77,58 @@ func parseRedisOutput(output string) (nodes []NodeInfo, err error) { slog.Debug("Parsing line looking for Follower", "content", line) if matches := primaryPortRegex.FindStringSubmatch(line); matches != nil { masterPort = matches[1] - nodes = append(nodes, NodeInfo{Address: fmt.Sprintf("%s:%s", masterHost, masterPort), Function: Primary}) + nodes = append(nodes, fmt.Sprintf("%s:%s", masterHost, masterPort)) + a.nodes[fmt.Sprintf("%s:%s", masterHost, masterPort)] = Primary } if matches := followerRegex.FindStringSubmatch(line); matches != nil { ip := matches[followerRegex.SubexpIndex("ip")] port := matches[followerRegex.SubexpIndex("port")] - nodes = append(nodes, NodeInfo{Address: fmt.Sprintf("%s:%s", ip, port), Function: Follower}) + nodes = append(nodes, fmt.Sprintf("%s:%s", ip, port)) + a.nodes[fmt.Sprintf("%s:%s", ip, port)] = Follower } } if err := scanner.Err(); err != nil { - return nodes, err - } - - return nodes, err -} - -// FindNodes returns a list of nodes in the cluster based on the redis info replication command -func (a *AclManager) FindNodes() (nodes []NodeInfo, err error) { - slog.Debug("Finding nodes") - replicationInfo, err := a.RedisClient.Info(context.Background(), "replication").Result() - if err != nil { - return nodes, err + return err } - nodes, err = parseRedisOutput(replicationInfo) - if err != nil { - return nodes, err + for _, node := range nodes { + if _, ok := a.nodes[node]; !ok { + delete(a.nodes, node) + } } - return nodes, err + return err } // CurrentFunction check if the current node is the Primary node -func (a *AclManager) CurrentFunction() (function int, err error) { +func (a *AclManager) CurrentFunction(ctx context.Context) (function int, err error) { slog.Debug("Check node current function") - replicationInfo, err := a.RedisClient.Info(context.Background(), "replication").Result() + err = a.findNodes(ctx) if err != nil { - return function, err + return Unknown, err } - - if role.MatchString(replicationInfo) { - return Primary, nil + if a.primary.Load() { + return Primary, err } return Follower, err } -// SyncAcls connects to master node and syncs the acls to the current node -func (a *AclManager) SyncAcls() (err error) { - slog.Debug("Syncing acls") - nodes, err := a.FindNodes() +func (a *AclManager) Primary(ctx context.Context) (primary *AclManager, err error) { + err = a.findNodes(ctx) if err != nil { - return err + return nil, err } - ctx := context.Background() - for _, node := range nodes { - if node.Function == Primary { - if a.Addr == node.Address { - return err - } - master := redis.NewClient(&redis.Options{ - Addr: node.Address, - Username: a.Username, - Password: a.Password, - }) - defer master.Close() - - added, deleted, err := mirrorAcls(ctx, master, a.RedisClient) - if err != nil { - return fmt.Errorf("error syncing acls: %v", err) - } - slog.Info("Synced acls", "added", added, "deleted", deleted) + for address, function := range a.nodes { + if function == Primary { + return New(address, a.Username, a.Password), err } } - return err + return nil, err } // Close closes the redis client @@ -181,15 +164,19 @@ func listAcls(ctx context.Context, client *redis.Client) (acls []string, err err return acls, nil } -// mirrorAcls returns a list of acls in the cluster based on the redis acl list command -func mirrorAcls(ctx context.Context, source *redis.Client, destination *redis.Client) (added []string, deleted []string, err error) { - slog.Debug("Mirroring acls") - sourceAcls, err := listAcls(ctx, source) +// SyncAcls connects to master node and syncs the acls to the current node +func (a *AclManager) SyncAcls(ctx context.Context, primary *AclManager) (added []string, deleted []string, err error) { + slog.Debug("Syncing acls") + if primary == nil { + return added, deleted, fmt.Errorf("no primary found") + } + + sourceAcls, err := listAcls(ctx, primary.RedisClient) if err != nil { return nil, nil, fmt.Errorf("error listing source acls: %v", err) } - destinationAcls, err := listAcls(ctx, destination) + destinationAcls, err := listAcls(ctx, a.RedisClient) if err != nil { return nil, nil, fmt.Errorf("error listing current acls: %v", err) } @@ -201,18 +188,16 @@ func mirrorAcls(ctx context.Context, source *redis.Client, destination *redis.Cl } // Delete ACLs not in source and remove from the toAdd map if present in destination - var insync uint for _, acl := range destinationAcls { username := strings.Split(acl, " ")[1] if _, found := toAdd[acl]; found { // If found in source, don't need to add, so remove from map delete(toAdd, acl) slog.Debug("ACL already in sync", "username", username) - insync++ } else { // If not found in source, delete from destination slog.Debug("Deleting ACL", "username", username) - if err := destination.Do(context.Background(), "ACL", "DELUSER", username).Err(); err != nil { + if err := a.RedisClient.Do(context.Background(), "ACL", "DELUSER", username).Err(); err != nil { return nil, nil, fmt.Errorf("error deleting acl: %v", err) } deleted = append(deleted, username) @@ -228,7 +213,7 @@ func mirrorAcls(ctx context.Context, source *redis.Client, destination *redis.Cl for i, s := range command { commandInterfce[i] = s } - if err := destination.Do(context.Background(), commandInterfce...).Err(); err != nil { + if err := a.RedisClient.Do(context.Background(), commandInterfce...).Err(); err != nil { return nil, nil, fmt.Errorf("error setting acl: %v", err) } added = append(added, username) @@ -242,22 +227,31 @@ func (a *AclManager) Loop(ctx context.Context) (err error) { ticker := time.NewTicker(viper.GetDuration("syncInterval") * time.Second) defer ticker.Stop() + var primary *AclManager for { select { case <-ctx.Done(): return err case <-ticker.C: - function, e := a.CurrentFunction() + function, e := a.CurrentFunction(ctx) if err != nil { - slog.Warn("unable to check if it's a primary", "message", e) - err = fmt.Errorf("unable to check if it's a primary: %w", e) + slog.Warn("unable to check if it's a Primary", "message", err) + err = fmt.Errorf("unable to check if it's a Primary: %w", err) } if function == Follower { - e = a.SyncAcls() + primary, err = a.Primary(ctx) + if err != nil { + slog.Warn("unable to find Primary", "message", e) + continue + } + var added, deleted []string + added, deleted, err = a.SyncAcls(ctx, primary) if err != nil { - slog.Warn("unable to sync acls from primary", "message", e) + slog.Warn("unable to sync acls from Primary", "message", err) + err = fmt.Errorf("unable to sync acls from Primary: %w", err) + continue } - err = fmt.Errorf("unable to sync acls from primary: %w", e) + slog.Info("Synced acls from Primary", "added", added, "deleted", deleted) } } } diff --git a/pkg/aclmanager/aclmanager_test.go b/pkg/aclmanager/aclmanager_test.go index c0f8fc6..1df135c 100644 --- a/pkg/aclmanager/aclmanager_test.go +++ b/pkg/aclmanager/aclmanager_test.go @@ -56,28 +56,23 @@ func TestFindNodes(t *testing.T) { tests := []struct { name string mockResp string - want []NodeInfo + want map[string]int wantErr bool + nodes map[string]int }{ { name: "parse master output", mockResp: primaryOutput, - want: []NodeInfo{ - { - Address: "172.21.0.3:6379", - Function: Follower, - }, + want: map[string]int{ + "172.21.0.3:6379": Follower, }, wantErr: false, }, { name: "parse Follower output", mockResp: followerOutput, - want: []NodeInfo{ - { - Address: "172.21.0.2:6379", - Function: Primary, - }, + want: map[string]int{ + "172.21.0.2:6379": Primary, }, wantErr: false, }, @@ -87,6 +82,20 @@ func TestFindNodes(t *testing.T) { want: nil, wantErr: true, }, + { + name: "ensure old nodes are removed", + mockResp: primaryOutput, + want: map[string]int{ + "172.21.0.3:6379": Follower, + }, + wantErr: false, + nodes: map[string]int{ + "192.168.0.1:6379": Follower, + "192.168.0.2:6379": Follower, + "192.168.0.3:6379": Follower, + "192.168.0.4:6379": Follower, + }, + }, } for _, tt := range tests { @@ -99,14 +108,30 @@ func TestFindNodes(t *testing.T) { } else { mock.ExpectInfo("replication").SetVal(tt.mockResp) } - aclManager := AclManager{RedisClient: redisClient} + aclManager := AclManager{RedisClient: redisClient, nodes: make(map[string]int)} + ctx := context.Background() - nodes, err := aclManager.FindNodes() + err := aclManager.findNodes(ctx) if (err != nil) != tt.wantErr { - t.Errorf("FindNodes() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("findNodes() error = %v, wantErr %v", err, tt.wantErr) return } - assert.Equal(t, tt.want, nodes) + if tt.name == "ensure old nodes are removed" { + for address, _ := range aclManager.nodes { + if _, ok := tt.nodes[address]; ok { + t.Errorf("findNodes() address %v shound not be found", address) + } + } + } + for address, function := range aclManager.nodes { + if wantFunction, ok := tt.want[address]; ok { + if wantFunction != function { + t.Errorf("findNodes() wanted function %v not found", function) + } + return + } + t.Errorf("findNodes() wanted address %v not found", address) + } }) } } @@ -188,14 +213,15 @@ func TestListAcls_Error(t *testing.T) { func TestMirrorAcls(t *testing.T) { tests := []struct { - name string - sourceAcls []interface{} - destinationAcls []interface{} - expectedDeleted []string - expectedAdded []string - listAclsError error - redisDoError error - wantErr bool + name string + sourceAcls []interface{} + destinationAcls []interface{} + expectedDeleted []string + expectedAdded []string + listAclsError error + redisDoError error + wantSourceErr bool + wantDestinationErr bool }{ { name: "ACLs synced with deletions", @@ -203,74 +229,110 @@ func TestMirrorAcls(t *testing.T) { destinationAcls: []interface{}{"user acl1", "user acl3"}, expectedDeleted: []string{"acl3"}, expectedAdded: []string{"acl2"}, - wantErr: false, + wantSourceErr: false, }, { - name: "ACLs synced with Error om SETUSER", - sourceAcls: []interface{}{"user acl1", "user acl2"}, - destinationAcls: []interface{}{"user acl1", "user acl3"}, - redisDoError: fmt.Errorf("DELUSER"), - wantErr: true, + name: "ACLs synced with Error om SETUSER", + sourceAcls: []interface{}{"user acl1", "user acl2"}, + destinationAcls: []interface{}{"user acl1", "user acl3"}, + redisDoError: fmt.Errorf("DELUSER"), + wantDestinationErr: true, }, { - name: "ACLs synced with Error on SETUSER", - sourceAcls: []interface{}{"user acl1", "user acl2"}, - destinationAcls: []interface{}{"user acl1", "user acl3"}, - redisDoError: fmt.Errorf("SETUSER"), - wantErr: true, + name: "ACLs synced with Error on SETUSER", + sourceAcls: []interface{}{"user acl1", "user acl2"}, + destinationAcls: []interface{}{"user acl1", "user acl3"}, + redisDoError: fmt.Errorf("SETUSER"), + wantSourceErr: false, + wantDestinationErr: true, }, { name: "No ACLs to delete", sourceAcls: []interface{}{"user acl1", "user acl2"}, destinationAcls: []interface{}{"user acl1", "user acl2"}, expectedDeleted: nil, - wantErr: false, + wantSourceErr: false, }, { name: "Error listing source ACLs", listAclsError: fmt.Errorf("error listing source ACLs"), - wantErr: true, + wantSourceErr: true, + }, + { + name: "Error listing destination ACLs", + listAclsError: fmt.Errorf("error listing destination ACLs"), + wantDestinationErr: true, + }, + { + name: "Invalid aclManagerPrimary", + listAclsError: fmt.Errorf("error listing destination ACLs"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - sourceClient, sourceMock := redismock.NewClientMock() - destinationClient, destMock := redismock.NewClientMock() + primaryClient, sourceMock := redismock.NewClientMock() + followerClient, destMock := redismock.NewClientMock() - if tt.listAclsError != nil { + aclManagerPrimary := &AclManager{RedisClient: primaryClient, nodes: make(map[string]int)} + aclManagerFollower := &AclManager{RedisClient: followerClient, nodes: make(map[string]int)} + + if tt.name == "Invalid aclManagerPrimary" { + aclManagerPrimary = nil + _, _, err := aclManagerFollower.SyncAcls(context.Background(), aclManagerPrimary) + assert.Error(t, err) + assert.Equal(t, "no primary found", err.Error()) + return + } + + if tt.listAclsError != nil && tt.wantSourceErr { sourceMock.ExpectDo("ACL", "LIST").SetErr(tt.listAclsError) } else { sourceMock.ExpectDo("ACL", "LIST").SetVal(tt.sourceAcls) } - if tt.listAclsError != nil { + if tt.listAclsError != nil && tt.wantDestinationErr { destMock.ExpectDo("ACL", "LIST").SetErr(tt.listAclsError) } else { destMock.ExpectDo("ACL", "LIST").SetVal(tt.destinationAcls) if tt.expectedDeleted != nil { - for _, acl := range tt.expectedDeleted { - if tt.wantErr && tt.redisDoError.Error() == "DELUSER" { - destMock.ExpectDo("ACL", "DELUSER", acl).SetErr(tt.redisDoError) + for _, username := range tt.expectedDeleted { + if tt.wantDestinationErr && tt.redisDoError.Error() == "DELUSER" { + destMock.ExpectDo("ACL", "DELUSER", username).SetErr(tt.redisDoError) continue } - destMock.ExpectDo("ACL", "DELUSER", acl).SetVal("OK") + destMock.ExpectDo("ACL", "DELUSER", username).SetVal("OK") } } if tt.expectedAdded != nil { - for _, acl := range tt.expectedAdded { - if tt.wantErr && tt.redisDoError.Error() == "SETUSER" { - destMock.ExpectDo("ACL", "SETUSER", acl).SetErr(tt.redisDoError) + for _, username := range tt.expectedAdded { + if tt.wantDestinationErr && tt.redisDoError.Error() == "SETUSER" { + destMock.ExpectDo("ACL", "SETUSER", username).SetErr(tt.redisDoError) continue } - destMock.ExpectDo("ACL", "SETUSER", acl).SetVal("OK") + destMock.ExpectDo("ACL", "SETUSER", username).SetVal("OK") } } } - added, deleted, err := mirrorAcls(context.Background(), sourceClient, destinationClient) - if (err != nil) != tt.wantErr { - t.Errorf("mirrorAcls() error = %v, wantErr %v", err, tt.wantErr) + added, deleted, err := aclManagerFollower.SyncAcls(context.Background(), aclManagerPrimary) + if err != nil { + if tt.wantSourceErr { + if tt.listAclsError != nil && !strings.Contains(err.Error(), tt.listAclsError.Error()) { + t.Errorf("mirrorAcls() error = %v, wantErr %v", err, tt.listAclsError) + } + if tt.redisDoError != nil && !strings.Contains(err.Error(), tt.redisDoError.Error()) { + t.Errorf("mirrorAcls() error = %v, wantErr %v", err, tt.redisDoError) + } + } + if !tt.wantDestinationErr { + if tt.listAclsError != nil && !strings.Contains(err.Error(), tt.listAclsError.Error()) { + t.Errorf("mirrorAcls() error = %v, wantErr %v", err, tt.listAclsError) + } + if tt.redisDoError != nil && !strings.Contains(err.Error(), tt.redisDoError.Error()) { + t.Errorf("mirrorAcls() error = %v, wantErr %v", err, tt.redisDoError) + } + } } if !reflect.DeepEqual(deleted, tt.expectedDeleted) { t.Errorf("mirrorAcls() deleted = %v, expectedDeleted %v", deleted, tt.expectedDeleted) @@ -282,14 +344,13 @@ func TestMirrorAcls(t *testing.T) { } } -func TestIsItPrimary(t *testing.T) { - // Sample Primary and Follower output for testing - +func TestCurrentFunction(t *testing.T) { tests := []struct { - name string - mockResp string - want int - wantErr bool + name string + mockResp string + want int + wantErr bool + RedisExpectInfoError error }{ { name: "parse Primary output", @@ -303,6 +364,13 @@ func TestIsItPrimary(t *testing.T) { want: Follower, wantErr: false, }, + { + name: "parse primary error", + mockResp: primaryOutput, + want: Unknown, + wantErr: true, + RedisExpectInfoError: fmt.Errorf("error"), + }, } for _, tt := range tests { @@ -310,13 +378,19 @@ func TestIsItPrimary(t *testing.T) { redisClient, mock := redismock.NewClientMock() // Mocking the response for the Info function - mock.ExpectInfo("replication").SetVal(tt.mockResp) - aclManager := AclManager{RedisClient: redisClient} - - nodes, err := aclManager.CurrentFunction() + if tt.wantErr { + mock.ExpectInfo("replication").SetErr(tt.RedisExpectInfoError) + } else { + mock.ExpectInfo("replication").SetVal(tt.mockResp) + } + aclManager := AclManager{RedisClient: redisClient, nodes: make(map[string]int)} + ctx := context.Background() + nodes, err := aclManager.CurrentFunction(ctx) if (err != nil) != tt.wantErr { - t.Errorf("FindNodes() error = %v, wantErr %v", err, tt.wantErr) - return + if !strings.Contains(err.Error(), tt.RedisExpectInfoError.Error()) { + t.Errorf("findNodes() error = %v, wantErr %v", err, tt.wantErr) + return + } } assert.Equal(t, tt.want, nodes) @@ -356,11 +430,79 @@ func TestCurrentFunction_Error(t *testing.T) { // Mocking the response for the Info function mock.ExpectInfo("replication").SetErr(fmt.Errorf("error")) aclManager := AclManager{RedisClient: redisClient} + ctx := context.Background() - _, err := aclManager.CurrentFunction() + _, err := aclManager.CurrentFunction(ctx) assert.Error(t, err) } +func TestAclManager_Primary(t *testing.T) { + tests := []struct { + name string + mockResp string + want string + wantErr bool + }{ + { + name: "parse master output", + mockResp: primaryOutput, + wantErr: false, + }, + { + name: "parse Follower output", + mockResp: followerOutput, + want: "172.21.0.2:6379", + wantErr: false, + }, + { + name: "error on replicationInfo", + mockResp: followerOutput, + wantErr: true, + }, + { + name: "username and password", + mockResp: followerOutput, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + redisClient, mock := redismock.NewClientMock() + + // Mocking the response for the Info function + if tt.wantErr { + mock.ExpectInfo("replication").SetErr(fmt.Errorf("error")) + } else { + mock.ExpectInfo("replication").SetVal(tt.mockResp) + } + aclManager := AclManager{RedisClient: redisClient, Username: "username", Password: "password", nodes: make(map[string]int)} + ctx := context.Background() + + primary, err := aclManager.Primary(ctx) + if tt.wantErr { + assert.Error(t, err) + assert.Nil(t, primary) + return + } + + if tt.name == "username and password" { + assert.Equal(t, aclManager.Username, primary.Username) + assert.Equal(t, aclManager.Password, primary.Password) + return + } + + assert.NoError(t, err) + if tt.want == "" { + assert.Nil(t, primary) + return + } + assert.NotNil(t, primary) + assert.Equal(t, tt.want, primary.Addr) + }) + } +} + func TestAclManager_Loop(t *testing.T) { viper.Set("syncInterval", 4) tests := []struct { @@ -370,20 +512,22 @@ func TestAclManager_Loop(t *testing.T) { expectError error }{ { - name: "primary node", + name: "Primary node", aclManager: &AclManager{ Addr: "localhost:6379", Password: "password", Username: "username", + nodes: make(map[string]int), }, wantErr: false, }, { - name: "primary node with error", + name: "Primary node with error", aclManager: &AclManager{ Addr: "localhost:6379", Password: "password", Username: "username", + nodes: make(map[string]int), }, wantErr: true, expectError: fmt.Errorf("error"), @@ -394,21 +538,22 @@ func TestAclManager_Loop(t *testing.T) { Addr: "localhost:6379", Password: "password", Username: "username", + nodes: make(map[string]int), }, wantErr: true, - expectError: fmt.Errorf("unable to check if it's a primary"), + expectError: fmt.Errorf("unable to check if it's a Primary"), + }, + { + name: "follower node", + aclManager: &AclManager{ + Addr: "localhost:6379", + Password: "password", + Username: "username", + nodes: make(map[string]int), + }, + wantErr: false, + expectError: nil, }, - // TODO: refactor to be able to test this case - //{ - // name: "follower node", - // aclManager: &AclManager{ - // Addr: "localhost:6379", - // Password: "password", - // Username: "username", - // }, - // wantErr: false, - // expectError: fmt.Errorf("error syncing acls"), - //}, } for _, tt := range tests { @@ -417,7 +562,7 @@ func TestAclManager_Loop(t *testing.T) { tt.aclManager.RedisClient = redisClient if tt.wantErr { - if tt.name == "primary node" { + if tt.name == "Primary node" { mock.ExpectInfo("replication").SetErr(fmt.Errorf("error")) mock.ExpectInfo("replication").SetErr(fmt.Errorf("error")) mock.ExpectInfo("replication").SetErr(fmt.Errorf("error")) @@ -438,7 +583,7 @@ func TestAclManager_Loop(t *testing.T) { mock.ExpectInfo("replication").SetVal(followerOutput) } } else { - if tt.name == "primary node" { + if tt.name == "Primary node" { mock.ExpectInfo("replication").SetVal(primaryOutput) mock.ExpectInfo("replication").SetVal(primaryOutput) mock.ExpectInfo("replication").SetVal(primaryOutput) @@ -446,7 +591,6 @@ func TestAclManager_Loop(t *testing.T) { mock.ExpectInfo("replication").SetVal(primaryOutput) mock.ExpectInfo("replication").SetVal(primaryOutput) mock.ExpectInfo("replication").SetVal(primaryOutput) - } } @@ -492,20 +636,20 @@ func TestClosePanic(t *testing.T) { assert.Panics(t, func() { aclManager.Close() }) } -func BenchmarkParseRedisOutputFollower(b *testing.B) { - for i := 0; i < b.N; i++ { - _, err := parseRedisOutput(followerOutput) - if err != nil { - b.Fatal(err) - } - } -} - -func BenchmarkParseRedisOutputMaster(b *testing.B) { - for i := 0; i < b.N; i++ { - _, err := parseRedisOutput(primaryOutput) - if err != nil { - b.Fatal(err) - } - } -} +//func BenchmarkParseRedisOutputFollower(b *testing.B) { +// for i := 0; i < b.N; i++ { +// _, err := parseRedisOutput(followerOutput) +// if err != nil { +// b.Fatal(err) +// } +// } +//} +// +//func BenchmarkParseRedisOutputMaster(b *testing.B) { +// for i := 0; i < b.N; i++ { +// _, err := parseRedisOutput(primaryOutput) +// if err != nil { +// b.Fatal(err) +// } +// } +//}