Skip to content

Commit

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

This patch introduces new command for the replicaset module.

```
add [--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 add
into local cluster config in case of `cluster config` orchestrator and
directly into all instances of replicaset in case of `cartridge`
orchestrator. Command supports `cartridge` and `cluster config`
orchestrators only for the entire application.

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
  (vshard-group in the Cartridge case) to specify a group to add a role;
- `--force (-f)` skips instances not found locally in `cluster
  config` orchestrator.

Closes #914
  • Loading branch information
themilchenko authored and oleg-jukovec committed Aug 27, 2024
1 parent 3464819 commit 690aa45
Show file tree
Hide file tree
Showing 19 changed files with 903 additions and 12 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- `tt cluster replicaset roles add`: command to add roles in config scope provided by flags.
- `tt replicaset roles remove`: command to remove roles in the tarantool replicaset with cluster
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.

### Fixed

Expand Down
120 changes: 120 additions & 0 deletions cli/cmd/replicaset.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ var (
replicasetIntegrityPrivateKey string
replicasetBootstrapVshard bool
replicasetCartridgeReplicasetsFile string
replicasetGroupName string
replicasetReplicasetName string
replicasetInstanceName string
replicasetIsGlobal bool
rebootstrapConfirmed bool

replicasetUriHelp = " The URI can be specified in the following formats:\n" +
Expand Down Expand Up @@ -242,6 +245,54 @@ func newRebootstrapCmd() *cobra.Command {
return cmd
}

func newRolesCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "roles",
Short: "Adds or removes roles for Cartridge and Tarantool 3 orchestrator",
}

cmd.AddCommand(newRolesAddCmd())
return cmd
}

// newRolesAddCmd creates a "replicaset roles add" command.
func newRolesAddCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "add [--cartridge|--config|--custom] [-f] [--timeout secs]" +
"<APP_NAME:INSTANCE_NAME> <ROLE_NAME> [flags]",
Short: "Adds a role for Cartridge and Tarantool 3 orchestrator",
Long: "Adds 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,
internalReplicasetRolesAddModule, 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 (vshard-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 All @@ -257,6 +308,7 @@ func NewReplicasetCmd() *cobra.Command {
cmd.AddCommand(newVShardCmd())
cmd.AddCommand(newBootstrapCmd())
cmd.AddCommand(newRebootstrapCmd())
cmd.AddCommand(newRolesCmd())

return cmd
}
Expand Down Expand Up @@ -350,6 +402,31 @@ 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 == "" {
if len(ctx.RunningCtx.Instances) == 0 {
return fmt.Errorf("there are no running instances")
}
// Trying to find alive instance to create connection with it.
var err error
for _, i := range ctx.RunningCtx.Instances {
connOpts = makeConnOpts(
connector.UnixNetwork,
i.ConsoleSocket,
connectCtx,
)
var conn connector.Connector
conn, err = connector.Connect(connOpts)
if err == nil {
ctx.IsInstanceConnect = true
conn.Close()
break
}
}
if err != nil {
return fmt.Errorf("cannot connect to any instance from replicaset")
}
}
} else {
if isRunningCtxRequired {
return err
Expand Down Expand Up @@ -570,3 +647,46 @@ func internalReplicasetRebootstrapModule(cmdCtx *cmdcontext.CmdCtx, args []strin
Confirmed: rebootstrapConfirmed,
})
}

// internalReplicasetRolesAddModule is a "roles add" command for the replicaset module.
func internalReplicasetRolesAddModule(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 in which to add 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.RolesAdd(replicasetcmd.RolesAddCtx{
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,
})
}
85 changes: 85 additions & 0 deletions cli/replicaset/cartridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"

"github.com/apex/log"
Expand Down Expand Up @@ -205,6 +206,11 @@ 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)
}

// CartridgeApplication is an application with the Cartridge orchestrator.
type CartridgeApplication struct {
cachedDiscoverer
Expand Down Expand Up @@ -662,6 +668,85 @@ 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 {
if len(c.runningCtx.Instances) == 0 {
return fmt.Errorf("failed to add role: there are no running instances")
}

targetReplicaset, err := getReplicasetByAlias(c.replicasets.Replicasets, ctx.ReplicasetName)
if err != nil {
return err
}

instances := filterInstances(c.runningCtx.Instances, func(
inst running.InstanceCtx) bool {
return slices.ContainsFunc(targetReplicaset.Instances, func(i Instance) bool {
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
}

eval := func(instance running.InstanceCtx, evaler connector.Evaler) (bool, error) {
return true, cartridgeEditReplicasets(evaler, []cartridgeEditReplicasetsOpts{
cartridgeEditOpt,
}, ctx.Timeout)
}
if err := EvalForeach(instances, InstanceEvalFunc(eval)); err != nil {
return err
}

newReplicasets, err := c.Discovery(SkipCache)
if err != nil {
return err
}
targetReplicaset, err = getReplicasetByAlias(newReplicasets.Replicasets, ctx.ReplicasetName)
if err != nil {
return err
}

if len(targetReplicaset.Roles) == 0 {
log.Infof("Now replicaset %s has no roles enabled", ctx.ReplicasetName)
} else {
log.Infof(
"Replicaset %s now has these roles enabled:",
ctx.ReplicasetName,
)

for _, role := range targetReplicaset.Roles {
if targetReplicaset.VshardGroup != "" {
log.Infof(" %s (%s)", role, targetReplicaset.VshardGroup)
} else {
log.Infof(" %s", role)
}
}
}
return nil
}

// getReplicasetByAlias searches for a replicaset by its alias in discovered slice
// of replicasets.
func getReplicasetByAlias(replicasets []Replicaset, alias string) (Replicaset, error) {
for _, r := range replicasets {
if r.Alias == alias {
return r, nil
}
}
return Replicaset{}, fmt.Errorf("failed to find replicaset %q", alias)
}

// getCartridgeInstanceInfo returns an additional instance information.
func getCartridgeInstanceInfo(
evaler connector.Evaler) (uuid string, rw bool, err error) {
Expand Down
9 changes: 9 additions & 0 deletions cli/replicaset/cartridge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +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.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{}

func TestCartridgeApplication_Demote(t *testing.T) {
app := replicaset.NewCartridgeApplication(running.RunningCtx{})
Expand Down Expand Up @@ -975,3 +977,10 @@ func TestCartridgeInstance_Expel(t *testing.T) {
assert.EqualError(t, err,
`expel is not supported for a single instance by "cartridge" orchestrator`)
}

func TestCartridgeInstance_RolesAdd(t *testing.T) {
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`)
}
Loading

0 comments on commit 690aa45

Please sign in to comment.