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 Nov 18, 2024
1 parent 5468a74 commit 62ec8a3
Show file tree
Hide file tree
Showing 8 changed files with 569 additions and 20 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Added

- `tt replicaset upgrade`: command to upgrade the schema on a Tarantool cluster.
* `-r (--replicaset)`: specify the replicaset name(s) to upgrade.
* `-t (--timeout)`: timeout for waiting the LSN synchronization (in seconds) (default 5).

### Changed

### Fixed
Expand Down
53 changes: 52 additions & 1 deletion cli/cmd/replicaset.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,42 @@ var (
replicasetIsGlobal bool
rebootstrapConfirmed bool

chosenReplicasetAliases []string
lsnTimeout int

replicasetUriHelp = " The URI can be specified in the following formats:\n" +
" * [tcp://][username:password@][host:port]\n" +
" * [unix://][username:password@]socketpath\n" +
" To specify relative path without `unix://` use `./`."
)

// newUpgradeCmd creates a "replicaset upgrade" command.
func newUpgradeCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "upgrade (<APP_NAME>) [flags]",
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(&chosenReplicasetAliases, "replicaset", "r",
[]string{}, "specify the replicaset name(s) to upgrade")

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

addOrchestratorFlags(cmd)
return cmd
}

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

cmd.AddCommand(newUpgradeCmd())
cmd.AddCommand(newStatusCmd())
cmd.AddCommand(newPromoteCmd())
cmd.AddCommand(newDemoteCmd())
Expand Down Expand Up @@ -490,6 +521,26 @@ 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,
}, replicasetcmd.UpgradeOpts{
ChosenReplicasetAliases: chosenReplicasetAliases,
LsnTimeout: lsnTimeout,
})
}

// internalReplicasetPromoteModule is a "promote" command for the replicaset module.
func internalReplicasetPromoteModule(cmdCtx *cmdcontext.CmdCtx, args []string) error {
var ctx replicasetCtx
Expand Down Expand Up @@ -561,7 +612,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
10 changes: 10 additions & 0 deletions cli/replicaset/cmd/lua/upgrade.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
local 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,
}
40 changes: 21 additions & 19 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 Down
Loading

0 comments on commit 62ec8a3

Please sign in to comment.