Skip to content

Commit

Permalink
tt: add command tt upgrade
Browse files Browse the repository at this point in the history
No commit message yet.
  • Loading branch information
mandesero committed Sep 6, 2024
1 parent 3e3e081 commit 34a3589
Show file tree
Hide file tree
Showing 5 changed files with 309 additions and 0 deletions.
1 change: 1 addition & 0 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ After that tt will be able to manage the application using 'replicaset_example'
NewKillCmd(),
NewLogCmd(),
NewEnableCmd(),
NewUpgradeCmd(),
)
if err := injectCmds(rootCmd); err != nil {
panic(err.Error())
Expand Down
48 changes: 48 additions & 0 deletions cli/cmd/upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package cmd

import (
"github.com/spf13/cobra"
"github.com/tarantool/tt/cli/cmd/internal"
"github.com/tarantool/tt/cli/cmdcontext"
"github.com/tarantool/tt/cli/modules"
"github.com/tarantool/tt/cli/running"
"github.com/tarantool/tt/cli/upgrade"
"github.com/tarantool/tt/cli/util"
)

func NewUpgradeCmd() *cobra.Command {
var upgradeCmd = &cobra.Command{
Use: "upgrade [<APP_NAME>]",
Short: "upgrade tarantool",
Run: func(cmd *cobra.Command, args []string) {
cmdCtx.CommandName = cmd.Name()
err := modules.RunCmd(&cmdCtx, cmd.CommandPath(), &modulesInfo,
upgradeInstance, args)
util.HandleCmdErr(cmd, err)
},
ValidArgsFunction: func(
cmd *cobra.Command,
args []string,
toComplete string) ([]string, cobra.ShellCompDirective) {
return internal.ValidArgsFunction(
cliOpts, &cmdCtx, cmd, toComplete,
running.ExtractAppNames,
running.ExtractInstanceNames)
},
}
return upgradeCmd
}

func upgradeInstance(cmdCtx *cmdcontext.CmdCtx, args []string) error {
if !isConfigExist(cmdCtx) {
return errNoConfig
}

var runningCtx running.RunningCtx
if err := running.FillCtx(cliOpts, cmdCtx, &runningCtx, args); err != nil {
return err
}

err := upgrade.Upgrade(runningCtx)
return err
}
18 changes: 18 additions & 0 deletions cli/upgrade/lua/instance.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
local uuid = box.info.uuid
local replica_uuids = {}

local isRO = (type(box.cfg) == 'function') or box.info.ro

if not isRO then
for _, elem in ipairs(box.info.replication) do
if elem.uuid ~= uuid then
table.insert(replica_uuids, elem.uuid)
end
end
end

return {
RO = isRO,
UUID = uuid,
ReplicUUIDs = replica_uuids,
}
11 changes: 11 additions & 0 deletions cli/upgrade/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.upgade)
if ok then
ok, err = pcall(box.snapshot)
end

return {
lsn = box.info.lsn,
iid = box.info.id,
err = ok and tostring(err) or nil,
}
231 changes: 231 additions & 0 deletions cli/upgrade/upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
package upgrade

import (
_ "embed"
"errors"
"fmt"
"os"
"time"

"github.com/jedib0t/go-pretty/v6/table"
"github.com/mitchellh/mapstructure"
"github.com/tarantool/tt/cli/connector"
"github.com/tarantool/tt/cli/running"
)

var TIMEOUT int = 5

//go:embed lua/instance.lua
var instanceInfoLuaScript string

//go:embed lua/upgrade.lua
var upgradeLuaScript string

type InstanceInfo struct {
RO bool `mapstructure:"RO"`
UUID string `mapstructure:"UUID"`
ReplicUUIDs []string `mapstructure:"ReplicUUIDs"`
}

type SyncInfo struct {
LSN uint32 `mapstructure:"lsn"`
IID uint32 `mapstructure:"iid"`
Err *string `mapstructure:"err"`
}

type MasterInfo struct {
Run running.InstanceCtx
ReplicUUIDs []string
}

func InternalUpgrade(conn connector.Connector) (SyncInfo, error) {
var upgradeInfo SyncInfo
res, err := conn.Eval(upgradeLuaScript, []any{}, connector.RequestOpts{})
if err != nil {
return upgradeInfo, err
}
if err := mapstructure.Decode(res[0], &upgradeInfo); err != nil {
return upgradeInfo, err
}
if upgradeInfo.Err != nil {
return upgradeInfo, errors.New(*upgradeInfo.Err)
}
return upgradeInfo, nil
}

func SplitInsances(runningCtx running.RunningCtx, masters *map[string]MasterInfo,
replics *map[string]running.InstanceCtx) error {

count := 0
for _, run := range runningCtx.Instances {
fullInstanceName := running.GetAppInstanceName(run)
conn, err := connector.Connect(connector.ConnectOpts{
Network: "unix",
Address: run.ConsoleSocket,
})
if err != nil {
return fmt.Errorf("[%s]: %w", fullInstanceName, err)
}

var instanceInfo InstanceInfo
res, err := conn.Eval(instanceInfoLuaScript, []any{}, connector.RequestOpts{})
if err != nil {
return fmt.Errorf("[%s]: %w", fullInstanceName, err)
}

if err := mapstructure.Decode(res[0], &instanceInfo); err != nil {
return fmt.Errorf("[%s]: %w", fullInstanceName, err)
}
if instanceInfo.RO {
(*replics)[instanceInfo.UUID] = run
} else {
for _, uuid := range instanceInfo.ReplicUUIDs {
if m, exist := (*masters)[uuid]; exist {
return fmt.Errorf("instances %q and %q are both masters "+
"in the same replicaset",
fullInstanceName,
running.GetAppInstanceName(m.Run))
}
}
(*masters)[instanceInfo.UUID] = MasterInfo{
Run: run,
ReplicUUIDs: instanceInfo.ReplicUUIDs,
}
count += len(instanceInfo.ReplicUUIDs) + 1
}
}

if count != len(runningCtx.Instances) {
replicasetIndex := 1
usedReplicas := make(map[string]bool)

t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.SetStyle(table.StyleRounded)

t.AppendHeader(table.Row{"Replicaset", "Instance", "Role"})

for _, masterInfo := range *masters {
masterName := running.GetAppInstanceName(masterInfo.Run)
t.AppendRow([]interface{}{fmt.Sprintf("Replicaset %d", replicasetIndex),
masterName, "master"})

for _, replicaUUID := range masterInfo.ReplicUUIDs {
if replicRun, exists := (*replics)[replicaUUID]; exists {
replicaName := running.GetAppInstanceName(replicRun)
t.AppendRow([]interface{}{"", replicaName, "replica"})
usedReplicas[replicaUUID] = true
}
}
t.AppendSeparator()
replicasetIndex++
}

t.AppendSeparator()
for replicaUUID, replicRun := range *replics {
if !usedReplicas[replicaUUID] {
replicaName := running.GetAppInstanceName(replicRun)
t.AppendRow([]interface{}{"N/A", replicaName, "replica"})
}
}

t.Render()

return errors.New("some instances were not assigned to any replicaset, " +
"or not all replicasets include all of their associated instances")
}
return nil
}

func Upgrade(runningCtx running.RunningCtx) error {
var masterInstances = make(map[string]MasterInfo)
var replicInstances = make(map[string]running.InstanceCtx)

var err error
err = SplitInsances(runningCtx, &masterInstances, &replicInstances)
if err != nil {
return err
}

t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.SetStyle(table.StyleRounded)
t.AppendHeader(table.Row{"Replicaset", "Instance", "Role", "Upgrade-status"})

replicasetIndex := 1
for _, masterInfo := range masterInstances {
run := masterInfo.Run
fullMasterInstanceName := running.GetAppInstanceName(run)
var conn connector.Connector
conn, err = connector.Connect(connector.ConnectOpts{
Network: "unix",
Address: run.ConsoleSocket,
})
if err != nil {
err = fmt.Errorf("[%s]: %s", fullMasterInstanceName, err)
break
}
// upgrade master
var masterUpgradeInfo SyncInfo
masterUpgradeInfo, err = InternalUpgrade(conn)
if err != nil {
err = fmt.Errorf("[%s]: %s", fullMasterInstanceName, err)
break
}
t.AppendRow([]interface{}{fmt.Sprintf("Replicaset %d", replicasetIndex),
fullMasterInstanceName, "master", "ok"})

for _, replicaUUID := range masterInfo.ReplicUUIDs {
replRun := replicInstances[replicaUUID]
fullReplicInstanceName := running.GetAppInstanceName(replRun)
conn, err = connector.Connect(connector.ConnectOpts{
Network: "unix",
Address: replRun.ConsoleSocket,
})
if err != nil {
err = fmt.Errorf("[%s]: %s", fullReplicInstanceName, err)
break
}
ok := false
for i := 0; i < TIMEOUT; i++ {
var res []interface{}
res, err = conn.Eval(
fmt.Sprintf("return box.info.vclock[%d]",
masterUpgradeInfo.IID),
[]any{}, connector.RequestOpts{})
if err != nil {
break
}
var lsn uint32
switch v := res[0].(type) {
case uint16:
lsn = uint32(v)
case uint32:
lsn = v
}
if lsn >= masterUpgradeInfo.LSN {
ok = true
break
}
time.Sleep(time.Second)
}
if !ok {
err = fmt.Errorf("[%s]: LSN wait timeout", fullReplicInstanceName)
break
}
_, err = InternalUpgrade(conn)
if err != nil {
err = fmt.Errorf("[%s]: %s", fullReplicInstanceName, err)
break
}
t.AppendRow([]interface{}{"", fullReplicInstanceName, "replica", "ok"})
}
t.AppendSeparator()
if err != nil {
break
}
replicasetIndex++
}
t.Render()
return err
}

0 comments on commit 34a3589

Please sign in to comment.