-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
tt replicaset
: add subcommand downgrade
Closes #968 @TarantoolBot document Title: `tt replicaset downgrade` downgrades database schema. The `tt replicaset downgrade` command allows for a automate downgrade 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 downgrade the schema and take a snapshot: ```lua box.schema.downgrade(<..version..>) box.snapshot() ``` - **On Each Replica**: 1. Wait for the replica to apply all transactions produced by the `box.schema.downgrade` 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 downgrade process, the operation will halt, and an error report will be generated. --- - Specify the schema version for downgrade The `tt replicaset downgrade` command requires specifying the target version for the schema downgrade. This version should be provided using the `--version` (or `-v`) option. The version must follow the `x.x.x` format, where `x` represents a numerical value. To view the list of available downgrade versions, execute the following command in Tarantool: ```lua box.schema.downgrade_versions() ``` **Example:** ```bash $ tt replicaset downgrade [<APP_NAME> | <URI>] --version 3.0.0 ``` - 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 downgrade [<APP_NAME> | <URI>] -v 3.0.0 --timeout 10 ``` - Selecting Replicasets for Downgrade You can specify which replicaset(s) to downgrade by using the `--replicaset` or `-r` option to target specific replicaset names. **Example:** ```bash $ tt replicaset downgrade [<APP_NAME> | <URI>] -v 3.0.0 replicaset <RS_NAME_1> -r <RS_NAME_2> ... ``` This provides flexibility in downgrading only the desired parts of the cluster without affecting the entire system.
- Loading branch information
Showing
5 changed files
with
492 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package replicasetcmd | ||
|
||
import ( | ||
_ "embed" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/mitchellh/mapstructure" | ||
"github.com/tarantool/tt/cli/connector" | ||
"github.com/tarantool/tt/cli/replicaset" | ||
"github.com/tarantool/tt/cli/running" | ||
) | ||
|
||
// DowngradeOpts contains options used for the downgrade process. | ||
type DowngradeOpts struct { | ||
// List of replicaset names specified by the user for the downgrade. | ||
ChosenReplicasetAliases []string | ||
// Timeout period (in seconds) for waiting on LSN synchronization. | ||
LsnTimeout int | ||
// Version to downgrade the schema to. | ||
DowngradeVersion string | ||
} | ||
|
||
//go:embed lua/downgrade.lua | ||
var downgradeMasterLua string | ||
|
||
func filterComments(script string) string { | ||
var filteredLines []string | ||
lines := strings.Split(script, "\n") | ||
for _, line := range lines { | ||
trimmedLine := strings.TrimSpace(line) | ||
if !strings.HasPrefix(trimmedLine, "--") { | ||
filteredLines = append(filteredLines, line) | ||
} | ||
} | ||
return strings.Join(filteredLines, "\n") | ||
} | ||
|
||
// Downgrade downgrades tarantool schema. | ||
func Downgrade(discoveryCtx DiscoveryCtx, opts DowngradeOpts, | ||
connOpts connector.ConnectOpts) error { | ||
replicasets, err := getReplicasets(discoveryCtx) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
replicasets = fillAliases(replicasets) | ||
replicasetsToDowngrade, err := filterReplicasetsByAliases(replicasets, | ||
opts.ChosenReplicasetAliases) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return internalDowngrade(replicasetsToDowngrade, opts.LsnTimeout, | ||
opts.DowngradeVersion, connOpts) | ||
} | ||
|
||
func internalDowngrade(replicasets []replicaset.Replicaset, lsnTimeout int, version string, | ||
connOpts connector.ConnectOpts) error { | ||
for _, replicaset := range replicasets { | ||
err := downgradeReplicaset(replicaset, lsnTimeout, version, connOpts) | ||
if err != nil { | ||
fmt.Printf("• %s: error\n", replicaset.Alias) | ||
return fmt.Errorf("replicaset %s: %w", replicaset.Alias, err) | ||
} | ||
fmt.Printf("• %s: ok\n", replicaset.Alias) | ||
} | ||
return nil | ||
} | ||
|
||
func downgradeMaster(master *instanceMeta, version string) (syncInfo, error) { | ||
var downgradeInfo syncInfo | ||
fullMasterName := running.GetAppInstanceName(master.run) | ||
res, err := master.conn.Eval(filterComments(downgradeMasterLua), | ||
[]interface{}{version}, connector.RequestOpts{}) | ||
if err != nil { | ||
return downgradeInfo, fmt.Errorf( | ||
"failed to execute downgrade script on master instance - %s: %w", | ||
fullMasterName, err) | ||
} | ||
|
||
if err := mapstructure.Decode(res[0], &downgradeInfo); err != nil { | ||
return downgradeInfo, fmt.Errorf( | ||
"failed to decode response from master instance - %s: %w", | ||
fullMasterName, err) | ||
} | ||
|
||
if downgradeInfo.Err != nil { | ||
return downgradeInfo, fmt.Errorf( | ||
"master instance downgrade failed - %s: %s", | ||
fullMasterName, *downgradeInfo.Err) | ||
} | ||
return downgradeInfo, nil | ||
} | ||
|
||
func downgradeReplicaset(replicaset replicaset.Replicaset, lsnTimeout int, version string, | ||
connOpts connector.ConnectOpts) error { | ||
master, replicas, err := collectRWROInfo(replicaset, connOpts) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
defer closeConnectors(master, replicas) | ||
|
||
// Downgrade master instance, collect LSN and IID from master instance. | ||
downgradeInfo, err := downgradeMaster(master, version) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Downgrade replica instances. | ||
masterLSN := downgradeInfo.LSN | ||
masterIID := downgradeInfo.IID | ||
|
||
for _, replica := range replicas { | ||
fullReplicaName := running.GetAppInstanceName(replica.run) | ||
err := waitLSN(replica.conn, masterIID, masterLSN, lsnTimeout) | ||
if err != nil { | ||
return fmt.Errorf("can't ensure that downgrade operations performed on "+ | ||
"%s are replicated to %s to perform snapshotting on it: error "+ | ||
"waiting LSN %d in vclock component %d: %w", | ||
running.GetAppInstanceName(master.run), fullReplicaName, | ||
masterLSN, masterIID, err) | ||
} | ||
err = snapshot(&replica) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
local version = ... | ||
local allowed_versions = box.schema.downgrade_versions() | ||
|
||
local function is_version_allowed(version, allowed_versions) | ||
for _, allowed_version in ipairs(allowed_versions) do | ||
if allowed_version == version then | ||
return true | ||
end | ||
end | ||
return false | ||
end | ||
|
||
local function format_allowed_versions(versions) | ||
return "[" .. table.concat(versions, ", ") .. "]" | ||
end | ||
|
||
local function downgrade_schema(version) | ||
if not is_version_allowed(version, allowed_versions) then | ||
local err = ("Version '%s' is not allowed.\nAllowed versions: %s"):format( | ||
version, format_allowed_versions(allowed_versions) | ||
) | ||
return { | ||
lsn = box.info.lsn, | ||
iid = box.info.id, | ||
err = err, | ||
} | ||
end | ||
|
||
local ok, err = pcall(function() | ||
box.schema.downgrade(version) | ||
box.snapshot() | ||
end) | ||
|
||
return { | ||
lsn = box.info.lsn, | ||
iid = box.info.id, | ||
err = not ok and tostring(err) or nil, | ||
} | ||
end | ||
|
||
return downgrade_schema(version) |
Oops, something went wrong.