Skip to content

Commit

Permalink
tt replicaset: add subcommand upgrade
Browse files Browse the repository at this point in the history
Part of #924

@TarantoolBot document
Title: `tt replicaset upgrade` upgrades database schema.

The `tt replicaset upgrade` command allows for a automate upgrade of each
replicaset in a Tarantool cluster. The process is performed sequentially on
the master instance and its replicas to ensure data consistency. Below are
the steps involved:

For Each Replicaset:
- **On the Master Instance**:
  1. Run the following commands in sequence to upgrade the schema and take
  a snapshot:
     ```lua
     box.schema.upgrade()
     box.snapshot()
     ```

- **On Each Replica**:
  1. Wait for the replica to apply all transactions produced by the
  `box.schema.upgrade()` command executed on the master. This is done
  by monitoring the vector clocks (vclock) to ensure synchronization.
  2. Once the repica has caught up, run the following command to take
  a snapshot:
     ```lua
     box.snapshot()
     ```

> **Error Handling**: If any errors occur during the upgrade process, the
operation will halt, and an error report will be generated.

---

- Timeout for Synchronization

Replicas will wait for synchronization for a maximum of `Timeout` seconds.
The default timeout is set to 5 seconds, but this can be adjusted manually
using the `--timeout` option.

**Example:**
```bash
$ tt replicaset upgrade [<APP_NAME>] --timeout 10
```

- Selecting Replicasets for Upgrade

You can specify which replicaset(s) to upgrade by using the `--replicaset`
or `-r` option to target specific replicaset names.

**Example:**
```bash
$ tt replicaset upgrade [<APP_NAME> | <URI>] --replicaset <RS_NAME_1> -r <RS_NAME_2> ...
```

This provides flexibility in upgrading only the desired parts of the cluster
without affecting the entire system.
  • Loading branch information
mandesero committed Oct 5, 2024
1 parent 2a1ebb6 commit 019d9f5
Show file tree
Hide file tree
Showing 4 changed files with 325 additions and 23 deletions.
49 changes: 48 additions & 1 deletion cli/cmd/replicaset.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,35 @@ var (
" To specify relative path without `unix://` use `./`."
)

// newUpgradeCmd creates a "replicaset upgrade" command.
func newUpgradeCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "upgrade (<APP_NAME> | <URI>) [flags]\n\n" +
replicasetUriHelp,
DisableFlagsInUseLine: true,
Short: "Upgrade tarantool cluster",
Long: "Upgrade tarantool cluster.\n\n" +
libconnect.EnvCredentialsHelp + "\n\n",
Run: func(cmd *cobra.Command, args []string) {
cmdCtx.CommandName = cmd.Name()
err := modules.RunCmd(&cmdCtx, cmd.CommandPath(), &modulesInfo,
internalReplicasetUpgradeModule, args)
util.HandleCmdErr(cmd, err)
},
Args: cobra.ExactArgs(1),
}

cmd.Flags().StringArrayVarP(&replicasetcmd.ChosenReplicasetAliases, "replicaset", "r",
[]string{}, "specify the replicaset name(s) to upgrade")

cmd.Flags().IntVarP(&replicasetcmd.LsnTimeout, "timeout", "t", 5,
"timeout for waiting the LSN synchronization (in seconds)")

addOrchestratorFlags(cmd)
addTarantoolConnectFlags(cmd)
return cmd
}

// newStatusCmd creates a "replicaset status" command.
func newStatusCmd() *cobra.Command {
cmd := &cobra.Command{
Expand Down Expand Up @@ -340,6 +369,7 @@ func NewReplicasetCmd() *cobra.Command {
Aliases: []string{"rs"},
}

cmd.AddCommand(newUpgradeCmd())
cmd.AddCommand(newStatusCmd())
cmd.AddCommand(newPromoteCmd())
cmd.AddCommand(newDemoteCmd())
Expand Down Expand Up @@ -489,6 +519,23 @@ func replicasetFillCtx(cmdCtx *cmdcontext.CmdCtx, ctx *replicasetCtx, args []str
return nil
}

// internalReplicasetUpgradeModule is a "upgrade" command for the replicaset module.
func internalReplicasetUpgradeModule(cmdCtx *cmdcontext.CmdCtx, args []string) error {
var ctx replicasetCtx
if err := replicasetFillCtx(cmdCtx, &ctx, args, false); err != nil {
return err
}
if ctx.IsInstanceConnect {
defer ctx.Conn.Close()
}
return replicasetcmd.Upgrade(replicasetcmd.DiscoveryCtx{
IsApplication: ctx.IsApplication,
RunningCtx: ctx.RunningCtx,
Conn: ctx.Conn,
Orchestrator: ctx.Orchestrator,
})
}

// internalReplicasetPromoteModule is a "promote" command for the replicaset module.
func internalReplicasetPromoteModule(cmdCtx *cmdcontext.CmdCtx, args []string) error {
var ctx replicasetCtx
Expand Down Expand Up @@ -560,7 +607,7 @@ func internalReplicasetStatusModule(cmdCtx *cmdcontext.CmdCtx, args []string) er
if ctx.IsInstanceConnect {
defer ctx.Conn.Close()
}
return replicasetcmd.Status(replicasetcmd.StatusCtx{
return replicasetcmd.Status(replicasetcmd.DiscoveryCtx{
IsApplication: ctx.IsApplication,
RunningCtx: ctx.RunningCtx,
Conn: ctx.Conn,
Expand Down
11 changes: 11 additions & 0 deletions cli/replicaset/cmd/lua/upgrade.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
local ok, err
ok, err = pcall(box.schema.upgrade)
if ok then
ok, err = pcall(box.snapshot)
end

return {
lsn = box.info.lsn,
iid = box.info.id,
err = (not ok) and tostring(err) or nil,
}
46 changes: 24 additions & 22 deletions cli/replicaset/cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,41 +10,43 @@ import (
"github.com/tarantool/tt/cli/running"
)

// StatusCtx contains information about replicaset status command execution
// context.
type StatusCtx struct {
// DiscoveryCtx contains information about replicaset discovery.
type DiscoveryCtx struct {
// IsApplication true if an application passed.
IsApplication bool
// RunningCtx is an application running context.
RunningCtx running.RunningCtx
// Conn is an active connection to a passed instance.
Conn connector.Connector
// Orchestrator is a forced orchestator choice.
// Orchestrator is a forced orchestrator choice.
Orchestrator replicaset.Orchestrator
}

// Status shows a replicaset status.
func Status(statusCtx StatusCtx) error {
orchestratorType, err := getOrchestratorType(statusCtx.Orchestrator,
statusCtx.Conn, statusCtx.RunningCtx)
// GetReplicasets discovers and returns the list of replicasets.
func GetReplicasets(ctx DiscoveryCtx) (replicaset.Replicasets, error) {
orchestratorType, err := getOrchestratorType(ctx.Orchestrator, ctx.Conn, ctx.RunningCtx)
if err != nil {
return err
return replicaset.Replicasets{}, err
}

var orchestrator replicasetOrchestrator
if statusCtx.IsApplication {
if orchestrator, err = makeApplicationOrchestrator(
orchestratorType, statusCtx.RunningCtx, nil, nil); err != nil {
return err
}
if ctx.IsApplication {
orchestrator, err = makeApplicationOrchestrator(orchestratorType,
ctx.RunningCtx, nil, nil)
} else {
if orchestrator, err = makeInstanceOrchestrator(
orchestratorType, statusCtx.Conn); err != nil {
return err
}
orchestrator, err = makeInstanceOrchestrator(orchestratorType, ctx.Conn)
}

if err != nil {
return replicaset.Replicasets{}, err
}

replicasets, err := orchestrator.Discovery(replicaset.SkipCache)
return orchestrator.Discovery(replicaset.SkipCache)
}

// Status shows a replicaset status.
func Status(discoveryCtx DiscoveryCtx) error {
replicasets, err := GetReplicasets(discoveryCtx)
if err != nil {
return err
}
Expand All @@ -61,7 +63,7 @@ func statusReplicasets(replicasets replicaset.Replicasets) error {
fmt.Println("Orchestrator: ", replicasets.Orchestrator)
fmt.Println("Replicasets state:", replicasets.State)

replicasets = fillAliases(replicasets)
replicasets = FillAliases(replicasets)
replicasets = sortAliases(replicasets)

if len(replicasets.Replicasets) > 0 {
Expand All @@ -73,9 +75,9 @@ func statusReplicasets(replicasets replicaset.Replicasets) error {
return nil
}

// fillAliases fills missed aliases with UUID. The case: Tarantool 1.10 without
// FillAliases fills missed aliases with UUID. The case: Tarantool 1.10 without
// an orchestrator.
func fillAliases(replicasets replicaset.Replicasets) replicaset.Replicasets {
func FillAliases(replicasets replicaset.Replicasets) replicaset.Replicasets {
for i := range replicasets.Replicasets {
replicaset := &replicasets.Replicasets[i]
if replicaset.Alias == "" {
Expand Down
Loading

0 comments on commit 019d9f5

Please sign in to comment.