Skip to content

Commit

Permalink
replicaset: add command 'roles remove'
Browse files Browse the repository at this point in the history
@TarantoolBot document
Title: `tt replicaset roles remove` removes roles in the tarantool replicaset
with cluster config (3.0) or cartridge orchestrator.

This patch introduces new command for the replicaset module.

```
$ tt rs roles remove [--cartridge|--config|--custom] [-f] [--timeout secs]
    <APP_NAME:INSTANCE_NAME> <ROLE_NAME> [flags]
```

It is possible to provide `cartridge`, `config` or `custom` flag to
explicitly state which orchestrator to use. ROLE_NAME is a role to
remove from local cluster config in case of `cluster config`
orchestrator and directly from all instances of replicaset in case
of `cartridge` orchestrator. Command supports `cartridge` and `cluster config`
orchestrators only for the entire application. INSTANCE_NAME works only
for `cluster config` to provide instance name to remove role from.

There are flags supported by this command:
- `--global (-G)` for a global scope to add a role (only for `cluster config`
  orchestrator);
- `--instance (-i) string` for an application name target to specify
  an instance to add a role;
- `--replicaset (-r) string` for an application name target to specify
  a replicaset to add a role;
- `--group (-g) string` for an application name target to specify a group
  to specify a group to add a role (only for `cluster config`);
- `--force (-f)` skips instances not found locally in `cluster
  config` orchestrator.

Closes #916
  • Loading branch information
themilchenko authored and oleg-jukovec committed Sep 3, 2024
1 parent a2174c3 commit 75d732b
Show file tree
Hide file tree
Showing 21 changed files with 784 additions and 119 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
config (3.0) or cartridge orchestrator.
- `tt replicaset roles add`: command to add roles in the tarantool replicaset with
cluster config (3.0) or cartridge orchestrator.
- `tt replicaset roles remove`: command to remove roles in the tarantool replicaset with
cluster config (3.0) or cartridge orchestrator.

### Fixed

Expand Down
4 changes: 2 additions & 2 deletions cli/cluster/cmd/replicaset.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ type RolesChangeCtx struct {
}

// ChangeRole adds/removes a role by patching the cluster config.
func ChangeRole(uri *url.URL, ctx RolesChangeCtx, changeRoleFunc replicaset.ChangeRoleFunc) error {
func ChangeRole(uri *url.URL, ctx RolesChangeCtx, action replicaset.RolesChangerAction) error {
opts, err := ParseUriOpts(uri)
if err != nil {
return fmt.Errorf("invalid URL %q: %w", uri, err)
Expand All @@ -327,7 +327,7 @@ func ChangeRole(uri *url.URL, ctx RolesChangeCtx, changeRoleFunc replicaset.Chan
IsGlobal: ctx.IsGlobal,
RoleName: ctx.RoleName,
Force: ctx.Force,
}, changeRoleFunc)
}, action)
if err == nil {
log.Info("Done.")
}
Expand Down
4 changes: 2 additions & 2 deletions cli/cmd/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ func internalClusterReplicasetRolesAddModule(cmdCtx *cmdcontext.CmdCtx, args []s
}

rolesChangeCtx.RoleName = args[1]
return clustercmd.ChangeRole(uri, rolesChangeCtx, replicaset.AddRole)
return clustercmd.ChangeRole(uri, rolesChangeCtx, replicaset.RolesAdder{})
}

// internalClusterReplicasetRolesRemoveModule is a "cluster replicaset roles remove" command.
Expand All @@ -597,7 +597,7 @@ func internalClusterReplicasetRolesRemoveModule(cmdCtx *cmdcontext.CmdCtx, args
}

rolesChangeCtx.RoleName = args[1]
return clustercmd.ChangeRole(uri, rolesChangeCtx, replicaset.RemoveRole)
return clustercmd.ChangeRole(uri, rolesChangeCtx, replicaset.RolesRemover{})
}

// internalClusterFailoverSwitchModule is as "cluster failover switch" command
Expand Down
90 changes: 86 additions & 4 deletions cli/cmd/replicaset.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ func newRolesCmd() *cobra.Command {
}

cmd.AddCommand(newRolesAddCmd())
cmd.AddCommand(newRolesRemoveCmd())
return cmd
}

Expand Down Expand Up @@ -293,6 +294,44 @@ func newRolesAddCmd() *cobra.Command {
return cmd
}

// newRolesRemoveCmd creates a "replicaset roles remove" command.
func newRolesRemoveCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "remove [--cartridge|--config|--custom] [-f] [--timeout secs]" +
"<APP_NAME:INSTANCE_NAME> <ROLE_NAME> [flags]",
Short: "Removes a role for Cartridge and Tarantool 3 orchestrator",
Long: "Removes a role for Cartridge and Tarantool 3 orchestrator",
Run: func(cmd *cobra.Command, args []string) {
cmdCtx.CommandName = cmd.Name()
err := modules.RunCmd(&cmdCtx, cmd.CommandPath(), &modulesInfo,
internalReplicasetRolesRemoveModule, args)
util.HandleCmdErr(cmd, err)
},
Args: cobra.ExactArgs(2),
}

cmd.Flags().StringVarP(&replicasetReplicasetName, "replicaset", "r", "",
"name of a target replicaset")
cmd.Flags().StringVarP(&replicasetGroupName, "group", "g", "",
"name of a target group (vhsard-group in the Cartridge case)")
cmd.Flags().StringVarP(&replicasetInstanceName, "instance", "i", "",
"name of a target instance")
cmd.Flags().BoolVarP(&replicasetIsGlobal, "global", "G", false,
"global config context")

addOrchestratorFlags(cmd)
addTarantoolConnectFlags(cmd)
cmd.Flags().BoolVarP(&replicasetForce, "force", "f", false,
"to force a promotion:\n"+
" * config: skip instances not found locally\n"+
" * cartridge: force inconsistency")
cmd.Flags().IntVarP(&replicasetTimeout, "timeout", "",
replicasetcmd.DefaultTimeout, "adding timeout")
integrity.RegisterWithIntegrityFlag(cmd.Flags(), &replicasetIntegrityPrivateKey)

return cmd
}

// NewReplicasetCmd creates a replicaset command.
func NewReplicasetCmd() *cobra.Command {
cmd := &cobra.Command{
Expand Down Expand Up @@ -402,8 +441,8 @@ func replicasetFillCtx(cmdCtx *cmdcontext.CmdCtx, ctx *replicasetCtx, args []str
}
}
}
// In case of adding a role when user may not provide an instance.
if cmdCtx.CommandName == "add" && ctx.InstName == "" {
// In case of adding/removing role when user may not provide an instance.
if (cmdCtx.CommandName == "add" || cmdCtx.CommandName == "remove") && ctx.InstName == "" {
if len(ctx.RunningCtx.Instances) == 0 {
return fmt.Errorf("there are no running instances")
}
Expand Down Expand Up @@ -674,7 +713,7 @@ func internalReplicasetRolesAddModule(cmdCtx *cmdcontext.CmdCtx, args []string)
return err
}

return replicasetcmd.RolesAdd(replicasetcmd.RolesAddCtx{
return replicasetcmd.RolesChange(replicasetcmd.RolesChangeCtx{
InstName: ctx.InstName,
GroupName: replicasetGroupName,
ReplicasetName: replicasetReplicasetName,
Expand All @@ -688,5 +727,48 @@ func internalReplicasetRolesAddModule(cmdCtx *cmdcontext.CmdCtx, args []string)
Orchestrator: ctx.Orchestrator,
Force: replicasetForce,
Timeout: replicasetTimeout,
})
}, replicaset.RolesAdder{})
}

// internalReplicasetRolesRemoveModule is a "roles remove" command for the replicaset module.
func internalReplicasetRolesRemoveModule(cmdCtx *cmdcontext.CmdCtx, args []string) error {
var ctx replicasetCtx
if err := replicasetFillCtx(cmdCtx, &ctx, args, false); err != nil {
return err
}
defer ctx.Conn.Close()
if ctx.IsApplication && replicasetInstanceName == "" && ctx.InstName == "" &&
!replicasetIsGlobal && replicasetGroupName == "" && replicasetReplicasetName == "" {
return fmt.Errorf("there is no destination provided where to remove role")
}
if ctx.InstName != "" && replicasetInstanceName != "" &&
replicasetInstanceName != ctx.InstName {
return fmt.Errorf("there are different instance names passed after" +
" app name and in flag arg")
}
if replicasetInstanceName != "" {
ctx.InstName = replicasetInstanceName
}

collectors, publishers, err := createDataCollectorsAndDataPublishers(
cmdCtx.Integrity, replicasetIntegrityPrivateKey)
if err != nil {
return err
}

return replicasetcmd.RolesChange(replicasetcmd.RolesChangeCtx{
InstName: ctx.InstName,
GroupName: replicasetGroupName,
ReplicasetName: replicasetReplicasetName,
IsGlobal: replicasetIsGlobal,
RoleName: args[1],
Collectors: collectors,
Publishers: publishers,
IsApplication: ctx.IsApplication,
Conn: ctx.Conn,
RunningCtx: ctx.RunningCtx,
Orchestrator: ctx.Orchestrator,
Force: replicasetForce,
Timeout: replicasetTimeout,
}, replicaset.RolesRemover{})
}
54 changes: 35 additions & 19 deletions cli/replicaset/cartridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,10 @@ func (c *CartridgeInstance) BootstrapVShard(ctx VShardBootstrapCtx) error {
return nil
}

// RolesAdd adds role for a single instance by the Cartridge orchestrator.
func (c *CartridgeInstance) RolesAdd(ctx RolesChangeCtx) error {
return newErrRolesAddByInstanceNotSupported(OrchestratorCartridge)
// RolesChange adds/removes role for a single instance by the Cartridge orchestrator.
func (c *CartridgeInstance) RolesChange(ctx RolesChangeCtx,
action RolesChangerAction) error {
return newErrRolesChangeByInstanceNotSupported(OrchestratorCartridge, action)
}

// CartridgeApplication is an application with the Cartridge orchestrator.
Expand Down Expand Up @@ -315,7 +316,7 @@ func (c *CartridgeApplication) Demote(ctx DemoteCtx) error {
type cartridgeReplicasetConfig struct {
Alias string `yaml:"alias,omitempty"`
Instances []string `yaml:"instances"`
Roles []string `yaml:"roles"`
Roles []string `yaml:"roles,omitempty"`
Weight *float64 `yaml:"weight,omitempty"`
AllRW *bool `yaml:"all_rw,omitempty"`
VShardGroup *string `yaml:"vshard_group,omitempty"`
Expand Down Expand Up @@ -668,8 +669,9 @@ func (c *CartridgeApplication) BootstrapVShard(ctx VShardBootstrapCtx) error {
return nil
}

// RolesAdd adds role for an application by the Cartridge orchestrator.
func (c *CartridgeApplication) RolesAdd(ctx RolesChangeCtx) error {
// RolesChange adds/removes role for an application by the Cartridge orchestrator.
func (c *CartridgeApplication) RolesChange(ctx RolesChangeCtx,
action RolesChangerAction) error {
if len(c.runningCtx.Instances) == 0 {
return fmt.Errorf("failed to add role: there are no running instances")
}
Expand All @@ -685,18 +687,11 @@ func (c *CartridgeApplication) RolesAdd(ctx RolesChangeCtx) error {
return i.Alias == inst.InstName
})
})
if slices.Contains(targetReplicaset.Roles, ctx.RoleName) {
return fmt.Errorf("role %q already exists in replicaset %q",
ctx.RoleName, ctx.ReplicasetName)
}
targetReplicaset.Roles = append(targetReplicaset.Roles, ctx.RoleName)

cartridgeEditOpt := cartridgeEditReplicasetsOpts{
UUID: &targetReplicaset.UUID,
Roles: targetReplicaset.Roles,
}
if ctx.GroupName != "" {
cartridgeEditOpt.VshardGroup = &ctx.GroupName
cartridgeEditOpt, err := getRolesChangedOpts(targetReplicaset, action,
ctx.RoleName, ctx.GroupName)
if err != nil {
return err
}

eval := func(instance running.InstanceCtx, evaler connector.Evaler) (bool, error) {
Expand Down Expand Up @@ -736,7 +731,28 @@ func (c *CartridgeApplication) RolesAdd(ctx RolesChangeCtx) error {
return nil
}

// getReplicasetByAlias searches for a replicaset by its alias in discovered slice
// getRolesChangedOpts adds/removes role for replicaset roles list and returns
// options for updating a replciaset.
func getRolesChangedOpts(targetReplicaset Replicaset, action RolesChangerAction,
roleName, groupName string) (cartridgeEditReplicasetsOpts, error) {
var err error
targetReplicaset.Roles, err = action.Change(targetReplicaset.Roles, roleName)
if err != nil {
return cartridgeEditReplicasetsOpts{}, fmt.Errorf("failed to change role: %w", err)
}

cartridgeEditOpt := cartridgeEditReplicasetsOpts{
UUID: &targetReplicaset.UUID,
Roles: targetReplicaset.Roles,
}
if groupName != "" && action.Action() == AddAction {
cartridgeEditOpt.VshardGroup = &groupName
}

return cartridgeEditOpt, nil
}

// getReplicasetByAlias searches for replicaset by its alias in discovered slice
// of replicasets.
func getReplicasetByAlias(replicasets []Replicaset, alias string) (Replicaset, error) {
for _, r := range replicasets {
Expand Down Expand Up @@ -857,7 +873,7 @@ type cartridgeJoinServersOpts struct {
type cartridgeEditReplicasetsOpts struct {
UUID *string `msgpack:"uuid,omitempty"`
Alias *string `msgpack:"alias,omitempty"`
Roles []string `msgpack:"roles,omitempty"`
Roles []string `msgpack:"roles"`
AllRW *bool `msgpack:"all_rw,omitempty"`
Weight *float64 `msgpack:"weight,omitempty"`
VshardGroup *string `msgpack:"vshard_group,omitempty"`
Expand Down
35 changes: 29 additions & 6 deletions cli/replicaset/cartridge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ var _ replicaset.Demoter = &replicaset.CartridgeInstance{}
var _ replicaset.Expeller = &replicaset.CartridgeInstance{}
var _ replicaset.VShardBootstrapper = &replicaset.CartridgeInstance{}
var _ replicaset.Bootstrapper = &replicaset.CartridgeInstance{}
var _ replicaset.RolesAdder = &replicaset.CartridgeInstance{}
var _ replicaset.RolesChanger = &replicaset.CartridgeInstance{}

var _ replicaset.Discoverer = &replicaset.CartridgeApplication{}
var _ replicaset.Promoter = &replicaset.CartridgeApplication{}
var _ replicaset.Demoter = &replicaset.CartridgeApplication{}
var _ replicaset.Expeller = &replicaset.CartridgeApplication{}
var _ replicaset.Bootstrapper = &replicaset.CartridgeApplication{}
var _ replicaset.RolesAdder = &replicaset.CartridgeApplication{}
var _ replicaset.RolesChanger = &replicaset.CartridgeApplication{}

func TestCartridgeApplication_Demote(t *testing.T) {
app := replicaset.NewCartridgeApplication(running.RunningCtx{})
Expand Down Expand Up @@ -978,9 +978,32 @@ func TestCartridgeInstance_Expel(t *testing.T) {
`expel is not supported for a single instance by "cartridge" orchestrator`)
}

func TestCartridgeInstance_RolesAdd(t *testing.T) {
func TestCartridgeInstance_RolesChange(t *testing.T) {
cases := []struct {
name string
changeAction replicaset.RolesChangerAction
errMsg string
}{
{
name: "roles add",
changeAction: replicaset.RolesAdder{},
errMsg: "roles add is not supported for a single instance by" +
` "cartridge" orchestrator`,
},
{
name: "roles remove",
changeAction: replicaset.RolesRemover{},
errMsg: "roles remove is not supported for a single instance by" +
` "cartridge" orchestrator`,
},
}

inst := replicaset.NewCartridgeInstance(nil)
err := inst.RolesAdd(replicaset.RolesChangeCtx{})
assert.EqualError(t, err,
`roles add is not supported for a single instance by "cartridge" orchestrator`)

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := inst.RolesChange(replicaset.RolesChangeCtx{}, tc.changeAction)
assert.EqualError(t, err, tc.errMsg)
})
}
}
27 changes: 14 additions & 13 deletions cli/replicaset/cconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
_ "embed"
"errors"
"fmt"
"slices"
"strings"

"github.com/apex/log"
Expand Down Expand Up @@ -136,10 +135,11 @@ func (c *CConfigInstance) BootstrapVShard(ctx VShardBootstrapCtx) error {
return nil
}

// RolesAdd is not supported for a single instance by the centralized config
// RolesChange is not supported for a single instance by the centralized config
// orchestrator.
func (c *CConfigInstance) RolesAdd(ctx RolesChangeCtx) error {
return newErrRolesAddByInstanceNotSupported(OrchestratorCentralizedConfig)
func (c *CConfigInstance) RolesChange(ctx RolesChangeCtx,
changeRoleAction RolesChangerAction) error {
return newErrRolesChangeByInstanceNotSupported(OrchestratorCentralizedConfig, changeRoleAction)
}

// CConfigApplication is an application with the centralized config
Expand Down Expand Up @@ -472,8 +472,9 @@ func (c *CConfigApplication) Bootstrap(BootstrapCtx) error {
return newErrBootstrapByAppNotSupported(OrchestratorCentralizedConfig)
}

// RolesAdd adds role for an application by the centralized config orchestrator.
func (c *CConfigApplication) RolesAdd(ctx RolesChangeCtx) error {
// RolesChange adds/removes role for an application by the centralized config orchestrator.
func (c *CConfigApplication) RolesChange(ctx RolesChangeCtx,
changeRoleAction RolesChangerAction) error {
replicasets, err := c.Discovery(UseCache)
if err != nil {
return fmt.Errorf("failed to get replicasets: %w", err)
Expand Down Expand Up @@ -518,7 +519,7 @@ func (c *CConfigApplication) RolesAdd(ctx RolesChangeCtx) error {
log.Warn(msg)
}

isConfigPublished, err := c.rolesAdd(ctx)
isConfigPublished, err := c.rolesChange(ctx, changeRoleAction)
if isConfigPublished {
err = errors.Join(err, reloadCConfig(instances))
}
Expand Down Expand Up @@ -751,7 +752,8 @@ func (c *CConfigApplication) demoteElection(instanceCtx running.InstanceCtx,
return
}

func (c *CConfigApplication) rolesAdd(ctx RolesChangeCtx) (bool, error) {
func (c *CConfigApplication) rolesChange(ctx RolesChangeCtx,
action RolesChangerAction) (bool, error) {
if len(c.runningCtx.Instances) == 0 {
return false, fmt.Errorf("there are no running instances")
}
Expand Down Expand Up @@ -781,12 +783,11 @@ func (c *CConfigApplication) rolesAdd(ctx RolesChangeCtx) (bool, error) {
return false, err
}
}
if len(existingRoles) > 0 && slices.Index(existingRoles, ctx.RoleName) != -1 {
return false, fmt.Errorf("role %q already exists in %s",
ctx.RoleName, strings.Join(path.path, "/"))

existingRoles, err = action.Change(existingRoles, ctx.RoleName)
if err != nil {
return false, fmt.Errorf("failed to change roles: %w", err)
}
// If the role does not exist in requested path, append it.
existingRoles = append(existingRoles, ctx.RoleName)

pRoleTarget = append(pRoleTarget, patchRoleTarget{
path: path.path,
Expand Down
Loading

0 comments on commit 75d732b

Please sign in to comment.