From f1142b1c787065f3d4f2754a9b76ad26a0670f27 Mon Sep 17 00:00:00 2001 From: SIGSEGV Date: Mon, 27 Jul 2020 21:41:25 +0800 Subject: [PATCH] Support native ssh client (#615) --- .github/workflows/integrate.yaml | 3 + components/cluster/command/check.go | 3 + components/cluster/command/deploy.go | 5 +- components/cluster/command/display.go | 4 +- components/cluster/command/import.go | 4 +- components/cluster/command/reload.go | 4 +- components/cluster/command/root.go | 11 + components/cluster/command/scale_out.go | 3 +- components/dm/command/deploy.go | 3 +- components/playground/instance/process.go | 31 +-- components/playground/playground.go | 6 +- components/playground/utils/utils.go | 41 ---- pkg/cluster/ansible/config.go | 6 +- pkg/cluster/ansible/dirs.go | 10 +- pkg/cluster/ansible/inventory.go | 20 +- pkg/cluster/executor/ssh.go | 190 ++++++++++++++++-- pkg/cluster/operation/operation.go | 1 + pkg/cluster/task/builder.go | 5 +- pkg/cluster/task/context.go | 4 +- pkg/cluster/task/ssh.go | 7 +- pkg/exec/run.go | 4 +- pkg/localdata/constant.go | 6 + pkg/utils/exec.go | 48 +++++ pkg/utils/ioutil.go | 38 ++++ tests/tiup-cluster/script/cmd_subtest.sh | 40 ++-- tests/tiup-cluster/script/scale_core.sh | 26 ++- tests/tiup-cluster/script/scale_tools.sh | 34 ++-- tests/tiup-cluster/test_cmd.sh | 4 +- tests/tiup-cluster/test_cmd_no_cdc.sh | 4 +- .../tiup-cluster/test_cmd_with_native_ssh.sh | 8 + tests/tiup-cluster/test_scale_core.sh | 4 +- .../test_scale_core_with_native_ssh.sh | 8 + tests/tiup-cluster/test_scale_tools.sh | 4 +- .../test_scale_tools_with_native_ssh.sh | 8 + 34 files changed, 420 insertions(+), 177 deletions(-) create mode 100644 pkg/utils/exec.go create mode 100644 tests/tiup-cluster/test_cmd_with_native_ssh.sh create mode 100644 tests/tiup-cluster/test_scale_core_with_native_ssh.sh create mode 100644 tests/tiup-cluster/test_scale_tools_with_native_ssh.sh diff --git a/.github/workflows/integrate.yaml b/.github/workflows/integrate.yaml index 6f9dd9b4ba..63769a242c 100644 --- a/.github/workflows/integrate.yaml +++ b/.github/workflows/integrate.yaml @@ -24,6 +24,9 @@ jobs: - "test_scale_core" - "test_scale_tools" - "test_upgrade" + - "test_cmd_with_native_ssh" + - "test_scale_core_with_native_ssh" + - "test_scale_tools_with_native_ssh" # - "test_dm_cmd" env: working-directory: ${{ github.workspace }}/go/src/github.com/${{ github.repository }} diff --git a/components/cluster/command/check.go b/components/cluster/command/check.go index 725cb96c27..ed17c48a2c 100644 --- a/components/cluster/command/check.go +++ b/components/cluster/command/check.go @@ -160,6 +160,7 @@ func checkSystemInfo(s *cliutil.SSHConnectionProps, topo *spec.Specification, op s.IdentityFile, s.IdentityFilePassphrase, gOpt.SSHTimeout, + gOpt.NativeSSH, ). Mkdir(opt.user, inst.GetHost(), filepath.Join(task.CheckToolsPathDir, "bin")). CopyComponent( @@ -269,6 +270,7 @@ func checkSystemInfo(s *cliutil.SSHConnectionProps, topo *spec.Specification, op s.IdentityFile, s.IdentityFilePassphrase, gOpt.SSHTimeout, + gOpt.NativeSSH, ). Rmdir(inst.GetHost(), task.CheckToolsPathDir). BuildAsStep(fmt.Sprintf(" - Cleanup check files on %s:%d", inst.GetHost(), inst.GetSSHPort())) @@ -308,6 +310,7 @@ func checkSystemInfo(s *cliutil.SSHConnectionProps, topo *spec.Specification, op s.IdentityFile, s.IdentityFilePassphrase, gOpt.SSHTimeout, + gOpt.NativeSSH, ) resLines, err := handleCheckResults(ctx, host, opt, tf) if err != nil { diff --git a/components/cluster/command/deploy.go b/components/cluster/command/deploy.go index e9de5eb1e7..98aa00f3cb 100644 --- a/components/cluster/command/deploy.go +++ b/components/cluster/command/deploy.go @@ -251,6 +251,7 @@ func deploy(clusterName, clusterVersion, topoFile string, opt deployOptions) err sshConnProps.IdentityFile, sshConnProps.IdentityFilePassphrase, gOpt.SSHTimeout, + gOpt.NativeSSH, ). EnvInit(inst.GetHost(), globalOptions.User). Mkdir(globalOptions.User, inst.GetHost(), dirs...). @@ -277,7 +278,7 @@ func deploy(clusterName, clusterVersion, topoFile string, opt deployOptions) err // Deploy component // prepare deployment server t := task.NewBuilder(). - UserSSH(inst.GetHost(), inst.GetSSHPort(), globalOptions.User, gOpt.SSHTimeout). + UserSSH(inst.GetHost(), inst.GetSSHPort(), globalOptions.User, gOpt.SSHTimeout, gOpt.NativeSSH). Mkdir(globalOptions.User, inst.GetHost(), deployDir, logDir, filepath.Join(deployDir, "bin"), @@ -416,7 +417,7 @@ func buildMonitoredDeployTask( logDir := clusterutil.Abs(globalOptions.User, monitoredOptions.LogDir) // Deploy component t := task.NewBuilder(). - UserSSH(host, info.ssh, globalOptions.User, gOpt.SSHTimeout). + UserSSH(host, info.ssh, globalOptions.User, gOpt.SSHTimeout, gOpt.NativeSSH). Mkdir(globalOptions.User, host, deployDir, dataDir, logDir, filepath.Join(deployDir, "bin"), diff --git a/components/cluster/command/display.go b/components/cluster/command/display.go index 6fbb7692c0..3c95cf6ede 100644 --- a/components/cluster/command/display.go +++ b/components/cluster/command/display.go @@ -150,7 +150,7 @@ func destroyTombstoneIfNeed(clusterName string, metadata *spec.ClusterMeta, opt return perrs.AddStack(err) } - err = ctx.SetClusterSSH(topo, metadata.User, gOpt.SSHTimeout) + err = ctx.SetClusterSSH(topo, metadata.User, gOpt.SSHTimeout, gOpt.NativeSSH) if err != nil { return perrs.AddStack(err) } @@ -197,7 +197,7 @@ func displayClusterTopology(clusterName string, opt *operator.Options) error { return perrs.AddStack(err) } - err = ctx.SetClusterSSH(topo, metadata.User, gOpt.SSHTimeout) + err = ctx.SetClusterSSH(topo, metadata.User, gOpt.SSHTimeout, gOpt.NativeSSH) if err != nil { return perrs.AddStack(err) } diff --git a/components/cluster/command/import.go b/components/cluster/command/import.go index 0bb79b6faf..bd07a44aa1 100644 --- a/components/cluster/command/import.go +++ b/components/cluster/command/import.go @@ -104,7 +104,7 @@ func newImportCmd() *cobra.Command { } // parse config and import nodes - if err = ansible.ParseAndImportInventory(ansibleDir, ansibleCfgFile, clsMeta, inv, gOpt.SSHTimeout); err != nil { + if err = ansible.ParseAndImportInventory(ansibleDir, ansibleCfgFile, clsMeta, inv, gOpt.SSHTimeout, gOpt.NativeSSH); err != nil { return err } @@ -124,7 +124,7 @@ func newImportCmd() *cobra.Command { } // copy config files form deployment servers - if err = ansible.ImportConfig(clsName, clsMeta, gOpt.SSHTimeout); err != nil { + if err = ansible.ImportConfig(clsName, clsMeta, gOpt.SSHTimeout, gOpt.NativeSSH); err != nil { return err } diff --git a/components/cluster/command/reload.go b/components/cluster/command/reload.go index 88b04dfd0a..c3df7b3715 100644 --- a/components/cluster/command/reload.go +++ b/components/cluster/command/reload.go @@ -121,7 +121,7 @@ func buildReloadTask( logDir := clusterutil.Abs(metadata.User, inst.LogDir()) // Download and copy the latest component to remote if the cluster is imported from Ansible - tb := task.NewBuilder().UserSSH(inst.GetHost(), inst.GetSSHPort(), metadata.User, gOpt.SSHTimeout) + tb := task.NewBuilder().UserSSH(inst.GetHost(), inst.GetSSHPort(), metadata.User, gOpt.SSHTimeout, gOpt.NativeSSH) if inst.IsImported() { switch compName := inst.ComponentName(); compName { case spec.ComponentGrafana, spec.ComponentPrometheus, spec.ComponentAlertManager: @@ -198,7 +198,7 @@ func refreshMonitoredConfigTask( logDir := clusterutil.Abs(globalOptions.User, monitoredOptions.LogDir) // Generate configs t := task.NewBuilder(). - UserSSH(host, info.ssh, globalOptions.User, gOpt.SSHTimeout). + UserSSH(host, info.ssh, globalOptions.User, gOpt.SSHTimeout, gOpt.NativeSSH). MonitoredConfig( clusterName, comp, diff --git a/components/cluster/command/root.go b/components/cluster/command/root.go index dddc1fa450..0713c6e129 100644 --- a/components/cluster/command/root.go +++ b/components/cluster/command/root.go @@ -79,6 +79,11 @@ func init() { flags.ShowBacktrace = len(os.Getenv("TIUP_BACKTRACE")) > 0 cobra.EnableCommandSorting = false + nativeEnvVar := strings.ToLower(os.Getenv(localdata.EnvNameNativeSSHClient)) + if nativeEnvVar == "true" || nativeEnvVar == "1" || nativeEnvVar == "enable" { + gOpt.NativeSSH = true + } + rootCmd = &cobra.Command{ Use: cliutil.OsArgs0(), Short: "Deploy a TiDB cluster for production", @@ -106,6 +111,11 @@ func init() { teleCommand = getParentNames(cmd) + if gOpt.NativeSSH { + zap.L().Info("Native ssh client will be used", + zap.String(localdata.EnvNameNativeSSHClient, os.Getenv(localdata.EnvNameNativeSSHClient))) + } + return nil }, RunE: func(cmd *cobra.Command, args []string) error { @@ -123,6 +133,7 @@ func init() { // start/stop operations is 90s, the default value of this argument is better be longer than that rootCmd.PersistentFlags().Int64Var(&gOpt.OptTimeout, "wait-timeout", 120, "Timeout in seconds to wait for an operation to complete, ignored for operations that don't fit.") rootCmd.PersistentFlags().BoolVarP(&skipConfirm, "yes", "y", false, "Skip all confirmations and assumes 'yes'") + rootCmd.PersistentFlags().BoolVar(&gOpt.NativeSSH, "native-ssh", gOpt.NativeSSH, "Use the native SSH client installed on local system instead of the build-in one.") rootCmd.AddCommand( newCheckCmd(), diff --git a/components/cluster/command/scale_out.go b/components/cluster/command/scale_out.go index becbc85b34..dcf4e418b2 100644 --- a/components/cluster/command/scale_out.go +++ b/components/cluster/command/scale_out.go @@ -224,6 +224,7 @@ func buildScaleOutTask( sshConnProps.IdentityFile, sshConnProps.IdentityFilePassphrase, gOpt.SSHTimeout, + gOpt.NativeSSH, ). EnvInit(instance.GetHost(), metadata.User). Mkdir(globalOptions.User, instance.GetHost(), dirs...). @@ -246,7 +247,7 @@ func buildScaleOutTask( // Deploy component tb := task.NewBuilder(). - UserSSH(inst.GetHost(), inst.GetSSHPort(), metadata.User, gOpt.SSHTimeout). + UserSSH(inst.GetHost(), inst.GetSSHPort(), metadata.User, gOpt.SSHTimeout, gOpt.NativeSSH). Mkdir(metadata.User, inst.GetHost(), deployDir, logDir, filepath.Join(deployDir, "bin"), diff --git a/components/dm/command/deploy.go b/components/dm/command/deploy.go index 469497d2a5..3ea00fba89 100644 --- a/components/dm/command/deploy.go +++ b/components/dm/command/deploy.go @@ -211,6 +211,7 @@ func deploy(clusterName, clusterVersion, topoFile string, opt deployOptions) err sshConnProps.IdentityFile, sshConnProps.IdentityFilePassphrase, gOpt.SSHTimeout, + gOpt.NativeSSH, ). EnvInit(inst.GetHost(), globalOptions.User). Mkdir(globalOptions.User, inst.GetHost(), dirs...). @@ -232,7 +233,7 @@ func deploy(clusterName, clusterVersion, topoFile string, opt deployOptions) err logDir := clusterutil.Abs(globalOptions.User, inst.LogDir()) // Deploy component t := task.NewBuilder(). - UserSSH(inst.GetHost(), inst.GetSSHPort(), globalOptions.User, gOpt.SSHTimeout). + UserSSH(inst.GetHost(), inst.GetSSHPort(), globalOptions.User, gOpt.SSHTimeout, gOpt.NativeSSH). Mkdir(globalOptions.User, inst.GetHost(), deployDir, logDir, filepath.Join(deployDir, "bin"), diff --git a/components/playground/instance/process.go b/components/playground/instance/process.go index 4d6a3359c1..7a7d1e2b61 100644 --- a/components/playground/instance/process.go +++ b/components/playground/instance/process.go @@ -11,16 +11,9 @@ import ( "github.com/pingcap/tiup/pkg/environment" tiupexec "github.com/pingcap/tiup/pkg/exec" "github.com/pingcap/tiup/pkg/repository/v0manifest" + "github.com/pingcap/tiup/pkg/utils" ) -// ErrorWaitTimeout is used to represent timeout of a command -// Example: -// _ = syscall.Kill(cmd.Process.Pid, syscall.SIGKILL) -// if err := WaitContext(context.WithTimeout(context.Background(), 3), cmd); err == ErrorWaitTimeout { -// // Do something -// } -var ErrorWaitTimeout = errors.New("wait command timeout") - // Process represent process to be run by playground type Process interface { Start() error @@ -46,7 +39,7 @@ func (p *process) Start() error { // Wait implements Instance interface. func (p *process) Wait(ctx context.Context) error { - return WaitContext(ctx, p.cmd) + return utils.WaitContext(ctx, p.cmd) } // Pid implements Instance interface. @@ -97,23 +90,3 @@ func NewComponentProcess(ctx context.Context, dir, binPath, component string, ve return &process{cmd: cmd}, nil } - -// WaitContext wrap cmd.Wait with context -func WaitContext(ctx context.Context, cmd *exec.Cmd) error { - // We use cmd.Process.Wait instead of cmd.Wait because cmd.Wait is not reenterable - c := make(chan error, 1) - go func() { - if cmd == nil || cmd.Process == nil { - c <- nil - } else { - _, err := cmd.Process.Wait() - c <- err - } - }() - select { - case <-ctx.Done(): - return ErrorWaitTimeout - case err := <-c: - return err - } -} diff --git a/components/playground/playground.go b/components/playground/playground.go index 613fe5d37a..59f9c1a210 100755 --- a/components/playground/playground.go +++ b/components/playground/playground.go @@ -37,12 +37,12 @@ import ( "github.com/pingcap/errors" "github.com/pingcap/failpoint" "github.com/pingcap/tiup/components/playground/instance" - "github.com/pingcap/tiup/components/playground/utils" "github.com/pingcap/tiup/pkg/cluster/api" "github.com/pingcap/tiup/pkg/cluster/clusterutil" "github.com/pingcap/tiup/pkg/environment" "github.com/pingcap/tiup/pkg/localdata" "github.com/pingcap/tiup/pkg/repository/v0manifest" + "github.com/pingcap/tiup/pkg/utils" "golang.org/x/mod/semver" "golang.org/x/sync/errgroup" ) @@ -925,7 +925,7 @@ func (p *Playground) terminate(sig syscall.Signal, extraCmds ...*exec.Cmd) { } ctx, cancel := context.WithTimeout(context.Background(), killDeadline) defer cancel() - if err := inst.Wait(ctx); err == instance.ErrorWaitTimeout { + if err := inst.Wait(ctx); err == utils.ErrorWaitTimeout { _ = syscall.Kill(inst.Pid(), syscall.SIGKILL) } return nil @@ -936,7 +936,7 @@ func (p *Playground) terminate(sig syscall.Signal, extraCmds ...*exec.Cmd) { } ctx, cancel := context.WithTimeout(context.Background(), killDeadline) defer cancel() - if err := instance.WaitContext(ctx, cmd); err == instance.ErrorWaitTimeout { + if err := utils.WaitContext(ctx, cmd); err == utils.ErrorWaitTimeout { _ = syscall.Kill(cmd.Process.Pid, syscall.SIGKILL) } } diff --git a/components/playground/utils/utils.go b/components/playground/utils/utils.go index 64f9af3049..471be9043a 100644 --- a/components/playground/utils/utils.go +++ b/components/playground/utils/utils.go @@ -14,12 +14,8 @@ package utils import ( - "bufio" "fmt" - "os" "time" - - "github.com/pingcap/errors" ) // RetryOption is options for Retry() @@ -82,40 +78,3 @@ func Retry(doFunc func() error, opts ...RetryOption) error { return fmt.Errorf("operation exceeds the max retry attempts of %d", cfg.Attempts) } - -// TailN try get the latest n line of the file. -func TailN(fname string, n int) (lines []string, err error) { - file, err := os.Open(fname) - if err != nil { - return nil, errors.AddStack(err) - } - defer file.Close() - - estimateLineSize := 1024 - - stat, err := os.Stat(fname) - if err != nil { - return nil, errors.AddStack(err) - } - - start := int(stat.Size()) - n*estimateLineSize - if start < 0 { - start = 0 - } - - _, err = file.Seek(int64(start), 0 /*means relative to the origin of the file*/) - if err != nil { - return nil, errors.AddStack(err) - } - - scanner := bufio.NewScanner(file) - for scanner.Scan() { - lines = append(lines, scanner.Text()) - } - - if len(lines) > n { - lines = lines[len(lines)-n:] - } - - return -} diff --git a/pkg/cluster/ansible/config.go b/pkg/cluster/ansible/config.go index e0e6540516..6222af0de8 100644 --- a/pkg/cluster/ansible/config.go +++ b/pkg/cluster/ansible/config.go @@ -24,7 +24,7 @@ import ( ) // ImportConfig copies config files from cluster which deployed through tidb-ansible -func ImportConfig(name string, clsMeta *spec.ClusterMeta, sshTimeout int64) error { +func ImportConfig(name string, clsMeta *spec.ClusterMeta, sshTimeout int64, nativeClient bool) error { // there may be already cluster dir, skip create //if err := os.MkdirAll(meta.ClusterPath(name), 0755); err != nil { // return err @@ -42,7 +42,7 @@ func ImportConfig(name string, clsMeta *spec.ClusterMeta, sshTimeout int64) erro SSHKeySet( spec.ClusterPath(name, "ssh", "id_rsa"), spec.ClusterPath(name, "ssh", "id_rsa.pub")). - UserSSH(inst.GetHost(), inst.GetSSHPort(), clsMeta.User, sshTimeout). + UserSSH(inst.GetHost(), inst.GetSSHPort(), clsMeta.User, sshTimeout, nativeClient). CopyFile(filepath.Join(inst.DeployDir(), "conf", inst.ComponentName()+".toml"), spec.ClusterPath(name, spec.AnsibleImportedConfigPath, @@ -59,7 +59,7 @@ func ImportConfig(name string, clsMeta *spec.ClusterMeta, sshTimeout int64) erro SSHKeySet( spec.ClusterPath(name, "ssh", "id_rsa"), spec.ClusterPath(name, "ssh", "id_rsa.pub")). - UserSSH(inst.GetHost(), inst.GetSSHPort(), clsMeta.User, sshTimeout). + UserSSH(inst.GetHost(), inst.GetSSHPort(), clsMeta.User, sshTimeout, nativeClient). CopyFile(filepath.Join(inst.DeployDir(), "conf", inst.ComponentName()+".toml"), spec.ClusterPath(name, spec.AnsibleImportedConfigPath, diff --git a/pkg/cluster/ansible/dirs.go b/pkg/cluster/ansible/dirs.go index 6f8b203cc6..402018498e 100644 --- a/pkg/cluster/ansible/dirs.go +++ b/pkg/cluster/ansible/dirs.go @@ -31,7 +31,7 @@ var ( ) // parseDirs sets values of directories of component -func parseDirs(user string, ins spec.InstanceSpec, sshTimeout int64) (spec.InstanceSpec, error) { +func parseDirs(user string, ins spec.InstanceSpec, sshTimeout int64, nativeClient bool) (spec.InstanceSpec, error) { hostName, sshPort := ins.SSH() e := executor.NewSSHExecutor(executor.SSHConfig{ @@ -40,7 +40,7 @@ func parseDirs(user string, ins spec.InstanceSpec, sshTimeout int64) (spec.Insta User: user, KeyFile: SSHKeyPath(), // ansible generated keyfile Timeout: time.Second * time.Duration(sshTimeout), - }, false) // not using global sudo + }, false /* not using global sudo */, nativeClient) log.Debugf("Detecting deploy paths on %s...", hostName) stdout, err := readStartScript(e, ins.Role(), hostName, ins.GetMainPort()) @@ -242,7 +242,7 @@ func parseDirs(user string, ins spec.InstanceSpec, sshTimeout int64) (spec.Insta return ins, nil } -func parseTiflashConfig(e *executor.SSHExecutor, spec *spec.TiFlashSpec, fname string) error { +func parseTiflashConfig(e executor.Executor, spec *spec.TiFlashSpec, fname string) error { data, err := readFile(e, fname) if err != nil { return errors.AddStack(err) @@ -275,7 +275,7 @@ func parseTiflashConfigFromFileData(spec *spec.TiFlashSpec, data []byte) error { return nil } -func readFile(e *executor.SSHExecutor, fname string) (data []byte, err error) { +func readFile(e executor.Executor, fname string) (data []byte, err error) { cmd := fmt.Sprintf("cat %s", fname) stdout, stderr, err := e.Execute(cmd, false) if err != nil { @@ -285,7 +285,7 @@ func readFile(e *executor.SSHExecutor, fname string) (data []byte, err error) { return stdout, nil } -func readStartScript(e *executor.SSHExecutor, component, host string, port int) (string, error) { +func readStartScript(e executor.Executor, component, host string, port int) (string, error) { serviceFile := fmt.Sprintf("%s/%s-%d.service", systemdUnitPath, component, diff --git a/pkg/cluster/ansible/inventory.go b/pkg/cluster/ansible/inventory.go index 23506d2a2d..239f538edf 100644 --- a/pkg/cluster/ansible/inventory.go +++ b/pkg/cluster/ansible/inventory.go @@ -48,14 +48,14 @@ var ( ) // ParseAndImportInventory builds a basic ClusterMeta from the main Ansible inventory -func ParseAndImportInventory(dir, ansCfgFile string, clsMeta *spec.ClusterMeta, inv *aini.InventoryData, sshTimeout int64) error { +func ParseAndImportInventory(dir, ansCfgFile string, clsMeta *spec.ClusterMeta, inv *aini.InventoryData, sshTimeout int64, nativeClient bool) error { if err := parseGroupVars(dir, ansCfgFile, clsMeta, inv); err != nil { return err } for i := 0; i < len(clsMeta.Topology.TiDBServers); i++ { s := clsMeta.Topology.TiDBServers[i] - ins, err := parseDirs(clsMeta.User, s, sshTimeout) + ins, err := parseDirs(clsMeta.User, s, sshTimeout, nativeClient) if err != nil { return err } @@ -63,7 +63,7 @@ func ParseAndImportInventory(dir, ansCfgFile string, clsMeta *spec.ClusterMeta, } for i := 0; i < len(clsMeta.Topology.TiKVServers); i++ { s := clsMeta.Topology.TiKVServers[i] - ins, err := parseDirs(clsMeta.User, s, sshTimeout) + ins, err := parseDirs(clsMeta.User, s, sshTimeout, nativeClient) if err != nil { return err } @@ -71,7 +71,7 @@ func ParseAndImportInventory(dir, ansCfgFile string, clsMeta *spec.ClusterMeta, } for i := 0; i < len(clsMeta.Topology.PDServers); i++ { s := clsMeta.Topology.PDServers[i] - ins, err := parseDirs(clsMeta.User, s, sshTimeout) + ins, err := parseDirs(clsMeta.User, s, sshTimeout, nativeClient) if err != nil { return err } @@ -79,7 +79,7 @@ func ParseAndImportInventory(dir, ansCfgFile string, clsMeta *spec.ClusterMeta, } for i := 0; i < len(clsMeta.Topology.TiFlashServers); i++ { s := clsMeta.Topology.TiFlashServers[i] - ins, err := parseDirs(clsMeta.User, s, sshTimeout) + ins, err := parseDirs(clsMeta.User, s, sshTimeout, nativeClient) if err != nil { return err } @@ -87,7 +87,7 @@ func ParseAndImportInventory(dir, ansCfgFile string, clsMeta *spec.ClusterMeta, } for i := 0; i < len(clsMeta.Topology.PumpServers); i++ { s := clsMeta.Topology.PumpServers[i] - ins, err := parseDirs(clsMeta.User, s, sshTimeout) + ins, err := parseDirs(clsMeta.User, s, sshTimeout, nativeClient) if err != nil { return err } @@ -95,7 +95,7 @@ func ParseAndImportInventory(dir, ansCfgFile string, clsMeta *spec.ClusterMeta, } for i := 0; i < len(clsMeta.Topology.Drainers); i++ { s := clsMeta.Topology.Drainers[i] - ins, err := parseDirs(clsMeta.User, s, sshTimeout) + ins, err := parseDirs(clsMeta.User, s, sshTimeout, nativeClient) if err != nil { return err } @@ -103,7 +103,7 @@ func ParseAndImportInventory(dir, ansCfgFile string, clsMeta *spec.ClusterMeta, } for i := 0; i < len(clsMeta.Topology.Monitors); i++ { s := clsMeta.Topology.Monitors[i] - ins, err := parseDirs(clsMeta.User, s, sshTimeout) + ins, err := parseDirs(clsMeta.User, s, sshTimeout, nativeClient) if err != nil { return err } @@ -111,7 +111,7 @@ func ParseAndImportInventory(dir, ansCfgFile string, clsMeta *spec.ClusterMeta, } for i := 0; i < len(clsMeta.Topology.Alertmanager); i++ { s := clsMeta.Topology.Alertmanager[i] - ins, err := parseDirs(clsMeta.User, s, sshTimeout) + ins, err := parseDirs(clsMeta.User, s, sshTimeout, nativeClient) if err != nil { return err } @@ -119,7 +119,7 @@ func ParseAndImportInventory(dir, ansCfgFile string, clsMeta *spec.ClusterMeta, } for i := 0; i < len(clsMeta.Topology.Grafana); i++ { s := clsMeta.Topology.Grafana[i] - ins, err := parseDirs(clsMeta.User, s, sshTimeout) + ins, err := parseDirs(clsMeta.User, s, sshTimeout, nativeClient) if err != nil { return err } diff --git a/pkg/cluster/executor/ssh.go b/pkg/cluster/executor/ssh.go index 21577f60fd..30d1420487 100644 --- a/pkg/cluster/executor/ssh.go +++ b/pkg/cluster/executor/ssh.go @@ -14,8 +14,11 @@ package executor import ( + "bytes" + "context" "fmt" "os" + "os/exec" "path/filepath" "strconv" "strings" @@ -25,6 +28,7 @@ import ( "github.com/fatih/color" "github.com/joomcode/errorx" "github.com/pingcap/tiup/pkg/cliutil" + "github.com/pingcap/tiup/pkg/localdata" "github.com/pingcap/tiup/pkg/utils" "go.uber.org/zap" ) @@ -47,6 +51,11 @@ var ( var executeDefaultTimeout = time.Second * 60 +// This command will be execute once the NativeSSHExecutor is created. +// It's used to predict if the connection can establish success in the future. +// Its main purpose is to avoid sshpass hang when user speficied a wrong prompt. +var connectionTestCommand = "echo connection test, if killed, check the password prompt" + func init() { v := os.Getenv("TIUP_CLUSTER_EXECUTE_DEFAULT_TIMEOUT") if v != "" { @@ -61,13 +70,21 @@ func init() { } type ( - // SSHExecutor implements Executor with SSH as transportation layer. - SSHExecutor struct { + // EasySSHExecutor implements Executor with EasySSH as transportation layer. + EasySSHExecutor struct { Config *easyssh.MakeConfig Locale string // the locale used when executing the command Sudo bool // all commands run with this executor will be using sudo } + // NativeSSHExecutor implements Excutor with native SSH transportation layer. + NativeSSHExecutor struct { + Config *SSHConfig + Locale string // the locale used when executing the command + Sudo bool // all commands run with this executor will be using sudo + ConnectionTestResult error // test if the connection can be established in initialization phase + } + // SSHConfig is the configuration needed to establish SSH connection. SSHConfig struct { Host string // hostname of the SSH server @@ -81,28 +98,41 @@ type ( } ) -var _ Executor = &SSHExecutor{} +var _ Executor = &EasySSHExecutor{} +var _ Executor = &NativeSSHExecutor{} // NewSSHExecutor create a ssh executor. -func NewSSHExecutor(c SSHConfig, sudo bool) *SSHExecutor { - e := new(SSHExecutor) - e.Initialize(c) - e.Locale = "C" // default locale, hard coded for now - e.Sudo = sudo - return e -} - -// Initialize builds and initializes a SSHExecutor -func (e *SSHExecutor) Initialize(config SSHConfig) { +func NewSSHExecutor(c SSHConfig, sudo bool, native bool) Executor { // set default values - if config.Port <= 0 { - config.Port = 22 + if c.Port <= 0 { + c.Port = 22 + } + + if c.Timeout == 0 { + c.Timeout = time.Second * 5 // default timeout is 5 sec } - if config.Timeout == 0 { - config.Timeout = time.Second * 5 // default timeout is 5 sec + if native { + e := &NativeSSHExecutor{ + Config: &c, + Locale: "C", + Sudo: sudo, + } + if c.Password != "" || (c.KeyFile != "" && c.Passphrase != "") { + _, _, e.ConnectionTestResult = e.Execute(connectionTestCommand, false, executeDefaultTimeout) + } + return e } + e := new(EasySSHExecutor) + e.initialize(c) + e.Locale = "C" // default locale, hard coded for now + e.Sudo = sudo + return e +} + +// Initialize builds and initializes a EasySSHExecutor +func (e *EasySSHExecutor) initialize(config SSHConfig) { // build easyssh config e.Config = &easyssh.MakeConfig{ Server: config.Host, @@ -121,7 +151,7 @@ func (e *SSHExecutor) Initialize(config SSHConfig) { } // Execute run the command via SSH, it's not invoking any specific shell by default. -func (e *SSHExecutor) Execute(cmd string, sudo bool, timeout ...time.Duration) ([]byte, []byte, error) { +func (e *EasySSHExecutor) Execute(cmd string, sudo bool, timeout ...time.Duration) ([]byte, []byte, error) { // try to acquire root permission if e.Sudo || sudo { cmd = fmt.Sprintf("sudo -H -u root bash -c \"%s\"", cmd) @@ -181,7 +211,7 @@ func (e *SSHExecutor) Execute(cmd string, sudo bool, timeout ...time.Duration) ( // This function depends on `scp` (a tool from OpenSSH or other SSH implementation) // This function is based on easyssh.MakeConfig.Scp() but with support of copying // file from remote to local. -func (e *SSHExecutor) Transfer(src string, dst string, download bool) error { +func (e *EasySSHExecutor) Transfer(src string, dst string, download bool) error { if !download { return e.Config.Scp(src, dst) } @@ -207,3 +237,125 @@ func (e *SSHExecutor) Transfer(src string, dst string, download bool) error { return session.Run(fmt.Sprintf("cat %s", src)) } + +func (e *NativeSSHExecutor) prompt(def string) string { + if prom := os.Getenv(localdata.EnvNameSSHPassPrompt); prom != "" { + return prom + } + return def +} + +func (e *NativeSSHExecutor) configArgs(args []string) []string { + if e.Config.Timeout != 0 { + args = append(args, "-o", fmt.Sprintf("ConnectTimeout=%d", int64(e.Config.Timeout.Seconds()))) + } + if e.Config.Password != "" { + args = append([]string{"sshpass", "-p", e.Config.Password, "-P", e.prompt("password")}, args...) + } else if e.Config.KeyFile != "" { + args = append(args, "-i", e.Config.KeyFile) + if e.Config.Passphrase != "" { + args = append([]string{"sshpass", "-p", e.Config.Passphrase, "-P", e.prompt("passphrase")}, args...) + } + } + return args +} + +// Execute run the command via SSH, it's not invoking any specific shell by default. +func (e *NativeSSHExecutor) Execute(cmd string, sudo bool, timeout ...time.Duration) ([]byte, []byte, error) { + if e.ConnectionTestResult != nil { + return nil, nil, e.ConnectionTestResult + } + + // try to acquire root permission + if e.Sudo || sudo { + cmd = fmt.Sprintf("sudo -H -u root bash -c \"%s\"", cmd) + } + + // set a basic PATH in case it's empty on login + cmd = fmt.Sprintf("PATH=$PATH:/usr/bin:/usr/sbin %s", cmd) + + if e.Locale != "" { + cmd = fmt.Sprintf("export LANG=%s; %s", e.Locale, cmd) + } + + // run command on remote host + // default timeout is 60s in easyssh-proxy + if len(timeout) == 0 { + timeout = append(timeout, executeDefaultTimeout) + } + + ctx := context.Background() + if len(timeout) > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(context.Background(), timeout[0]) + defer cancel() + } + + args := []string{"ssh", "-o", "StrictHostKeyChecking=no"} + args = e.configArgs(args) // prefix and postfix args + args = append(args, fmt.Sprintf("%s@%s", e.Config.User, e.Config.Host), cmd) + + command := exec.CommandContext(ctx, args[0], args[1:]...) + + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + command.Stdout = stdout + command.Stderr = stderr + + err := command.Run() + + zap.L().Info("SSHCommand", + zap.String("host", e.Config.Host), + zap.Int("port", e.Config.Port), + zap.String("cmd", cmd), + zap.Error(err), + zap.String("stdout", stdout.String()), + zap.String("stderr", stderr.String())) + + if err != nil { + baseErr := ErrSSHExecuteFailed. + Wrap(err, "Failed to execute command over SSH for '%s@%s:%d'", e.Config.User, e.Config.Host, e.Config.Port). + WithProperty(ErrPropSSHCommand, cmd). + WithProperty(ErrPropSSHStdout, stdout). + WithProperty(ErrPropSSHStderr, stderr) + if len(stdout.Bytes()) > 0 || len(stderr.Bytes()) > 0 { + output := strings.TrimSpace(strings.Join([]string{stdout.String(), stderr.String()}, "\n")) + baseErr = baseErr. + WithProperty(cliutil.SuggestionFromFormat("Command output on remote host %s:\n%s\n", + e.Config.Host, + color.YellowString(output))) + } + return stdout.Bytes(), stderr.Bytes(), baseErr + } + + return stdout.Bytes(), stderr.Bytes(), err +} + +// Transfer copies files via SCP +// This function depends on `scp` (a tool from OpenSSH or other SSH implementation) +func (e *NativeSSHExecutor) Transfer(src string, dst string, download bool) error { + if e.ConnectionTestResult != nil { + return e.ConnectionTestResult + } + + args := []string{"scp", "-r", "-o", "StrictHostKeyChecking=no"} + args = e.configArgs(args) // prefix and postfix args + + if download { + targetPath := filepath.Dir(dst) + if err := utils.CreateDir(targetPath); err != nil { + return err + } + args = append(args, fmt.Sprintf("%s@%s:%s", e.Config.User, e.Config.Host, src), dst) + } else { + args = append(args, src, fmt.Sprintf("%s@%s:%s", e.Config.User, e.Config.Host, dst)) + } + + command := exec.Command(args[0], args[1:]...) + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + command.Stdout = stdout + command.Stderr = stderr + + return command.Run() +} diff --git a/pkg/cluster/operation/operation.go b/pkg/cluster/operation/operation.go index 965b84ea62..2644b07021 100644 --- a/pkg/cluster/operation/operation.go +++ b/pkg/cluster/operation/operation.go @@ -30,6 +30,7 @@ type Options struct { OptTimeout int64 // timeout in seconds for operations that support it, not to confuse with SSH timeout APITimeout int64 // timeout in seconds for API operations that support it, like transfering store leader IgnoreConfigCheck bool // should we ignore the config check result after init config + NativeSSH bool // should use native ssh client or builtin easy ssh // Some data will be retained when destroying instances RetainDataRoles []string diff --git a/pkg/cluster/task/builder.go b/pkg/cluster/task/builder.go index 96308fc923..75eecac986 100644 --- a/pkg/cluster/task/builder.go +++ b/pkg/cluster/task/builder.go @@ -38,6 +38,7 @@ func (b *Builder) RootSSH( port int, user, password, keyFile, passphrase string, sshTimeout int64, + nativeClient bool, ) *Builder { b.tasks = append(b.tasks, &RootSSH{ host: host, @@ -47,17 +48,19 @@ func (b *Builder) RootSSH( keyFile: keyFile, passphrase: passphrase, timeout: sshTimeout, + native: nativeClient, }) return b } // UserSSH append a UserSSH task to the current task collection -func (b *Builder) UserSSH(host string, port int, deployUser string, sshTimeout int64) *Builder { +func (b *Builder) UserSSH(host string, port int, deployUser string, sshTimeout int64, nativeClient bool) *Builder { b.tasks = append(b.tasks, &UserSSH{ host: host, port: port, deployUser: deployUser, timeout: sshTimeout, + native: nativeClient, }) return b } diff --git a/pkg/cluster/task/context.go b/pkg/cluster/task/context.go index 1dff6f1807..f42f48107d 100644 --- a/pkg/cluster/task/context.go +++ b/pkg/cluster/task/context.go @@ -29,7 +29,7 @@ func (ctx *Context) SetSSHKeySet(privateKeyPath string, publicKeyPath string) er } // SetClusterSSH set cluster user ssh executor in context. -func (ctx *Context) SetClusterSSH(topo *spec.Specification, deployUser string, sshTimeout int64) error { +func (ctx *Context) SetClusterSSH(topo *spec.Specification, deployUser string, sshTimeout int64, nativeClient bool) error { if len(ctx.PrivateKeyPath) == 0 { return errors.Errorf("context has no PrivateKeyPath") } @@ -44,7 +44,7 @@ func (ctx *Context) SetClusterSSH(topo *spec.Specification, deployUser string, s Timeout: time.Second * time.Duration(sshTimeout), } - e := executor.NewSSHExecutor(cf, false /* sudo */) + e := executor.NewSSHExecutor(cf, false /* sudo */, nativeClient) ctx.SetExecutor(in.GetHost(), e) } } diff --git a/pkg/cluster/task/ssh.go b/pkg/cluster/task/ssh.go index d550339ded..644fd1a271 100644 --- a/pkg/cluster/task/ssh.go +++ b/pkg/cluster/task/ssh.go @@ -34,6 +34,7 @@ type RootSSH struct { keyFile string // path to the private key file passphrase string // passphrase of the private key file timeout int64 // timeout in seconds when connecting via SSH + native bool // use native ssh client } // Execute implements the Task interface @@ -46,7 +47,7 @@ func (s *RootSSH) Execute(ctx *Context) error { KeyFile: s.keyFile, Passphrase: s.passphrase, Timeout: time.Second * time.Duration(s.timeout), - }, s.user != "root") // using sudo by default if user is not root + }, s.user != "root", s.native) // using sudo by default if user is not root ctx.SetExecutor(s.host, e) return nil @@ -74,6 +75,7 @@ type UserSSH struct { port int deployUser string timeout int64 + native bool } // Execute implements the Task interface @@ -84,8 +86,7 @@ func (s *UserSSH) Execute(ctx *Context) error { KeyFile: ctx.PrivateKeyPath, User: s.deployUser, Timeout: time.Second * time.Duration(s.timeout), - }, false) // not using sudo by default - + }, false /* not using sudo by default */, s.native) ctx.SetExecutor(s.host, e) return nil } diff --git a/pkg/exec/run.go b/pkg/exec/run.go index a2232aa3ae..8b29c96914 100644 --- a/pkg/exec/run.go +++ b/pkg/exec/run.go @@ -17,7 +17,6 @@ import ( "context" "encoding/json" "fmt" - "github.com/fatih/color" "math" "os" "os/exec" @@ -28,6 +27,7 @@ import ( "syscall" "time" + "github.com/fatih/color" "github.com/pingcap/errors" "github.com/pingcap/tiup/pkg/environment" "github.com/pingcap/tiup/pkg/localdata" @@ -247,7 +247,7 @@ func launchComponent(ctx context.Context, component string, version v0manifest.V p := &localdata.Process{ Component: component, CreatedTime: time.Now().Format(time.RFC3339), - Exec: binPath, + Exec: c.Args[0], Args: args, Dir: c.Dir, Env: c.Env, diff --git a/pkg/localdata/constant.go b/pkg/localdata/constant.go index 2fc90ebf62..2e7456d820 100644 --- a/pkg/localdata/constant.go +++ b/pkg/localdata/constant.go @@ -64,6 +64,12 @@ const ( // EnvTag is the tag of the running component EnvTag = "TIUP_TAG" + // EnvNameSSHPassPrompt is the variable name by which user specific the password prompt for sshpass + EnvNameSSHPassPrompt = "TIUP_SSHPASS_PROMPT" + + // EnvNameNativeSSHClient is the variable name by which user can specific use natiive ssh client or not + EnvNameNativeSSHClient = "TIUP_NATIVE_SSH" + // MetaFilename represents the process meta file name MetaFilename = "tiup_process_meta" ) diff --git a/pkg/utils/exec.go b/pkg/utils/exec.go new file mode 100644 index 0000000000..3458b97544 --- /dev/null +++ b/pkg/utils/exec.go @@ -0,0 +1,48 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "context" + "errors" + "os/exec" +) + +// ErrorWaitTimeout is used to represent timeout of a command +// Example: +// _ = syscall.Kill(cmd.Process.Pid, syscall.SIGKILL) +// if err := WaitContext(context.WithTimeout(context.Background(), 3), cmd); err == ErrorWaitTimeout { +// // Do something +// } +var ErrorWaitTimeout = errors.New("wait command timeout") + +// WaitContext wrap cmd.Wait with context +func WaitContext(ctx context.Context, cmd *exec.Cmd) error { + // We use cmd.Process.Wait instead of cmd.Wait because cmd.Wait is not reenterable + c := make(chan error, 1) + go func() { + if cmd == nil || cmd.Process == nil { + c <- nil + } else { + _, err := cmd.Process.Wait() + c <- err + } + }() + select { + case <-ctx.Done(): + return ErrorWaitTimeout + case err := <-c: + return err + } +} diff --git a/pkg/utils/ioutil.go b/pkg/utils/ioutil.go index de13371662..84342a0acb 100644 --- a/pkg/utils/ioutil.go +++ b/pkg/utils/ioutil.go @@ -15,6 +15,7 @@ package utils import ( "archive/tar" + "bufio" "compress/gzip" "crypto/sha1" "encoding/hex" @@ -202,3 +203,40 @@ func Checksum(file string) (string, error) { checksum := hex.EncodeToString(sha1Writter.Sum(nil)) return checksum, nil } + +// TailN try get the latest n line of the file. +func TailN(fname string, n int) (lines []string, err error) { + file, err := os.Open(fname) + if err != nil { + return nil, errors.AddStack(err) + } + defer file.Close() + + estimateLineSize := 1024 + + stat, err := os.Stat(fname) + if err != nil { + return nil, errors.AddStack(err) + } + + start := int(stat.Size()) - n*estimateLineSize + if start < 0 { + start = 0 + } + + _, err = file.Seek(int64(start), 0 /*means relative to the origin of the file*/) + if err != nil { + return nil, errors.AddStack(err) + } + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + + if len(lines) > n { + lines = lines[len(lines)-n:] + } + + return +} diff --git a/tests/tiup-cluster/script/cmd_subtest.sh b/tests/tiup-cluster/script/cmd_subtest.sh index 7aa4bcee37..8386f0e9cc 100755 --- a/tests/tiup-cluster/script/cmd_subtest.sh +++ b/tests/tiup-cluster/script/cmd_subtest.sh @@ -5,6 +5,7 @@ function cmd_subtest() { version=$1 test_cdc=$2 + native_ssh=$3 name="test_cmd_$RANDOM" if [ $test_cdc = true ]; then @@ -13,40 +14,45 @@ function cmd_subtest() { topo=./topo/full_without_cdc.yaml fi - tiup-cluster check $topo -i ~/.ssh/id_rsa --enable-mem --enable-cpu --apply + client="" + if [ $native_ssh == true ]; then + client="--native-ssh" + fi + + tiup-cluster $client check $topo -i ~/.ssh/id_rsa --enable-mem --enable-cpu --apply - tiup-cluster --yes check $topo -i ~/.ssh/id_rsa + tiup-cluster $client --yes check $topo -i ~/.ssh/id_rsa - tiup-cluster --yes deploy $name $version $topo -i ~/.ssh/id_rsa + tiup-cluster $client --yes deploy $name $version $topo -i ~/.ssh/id_rsa - tiup-cluster list | grep "$name" + tiup-cluster $client list | grep "$name" - tiup-cluster audit | grep "deploy $name $version" + tiup-cluster $client audit | grep "deploy $name $version" # Get the audit id can check it just runnable id=`tiup-cluster audit | grep "deploy $name $version" | awk '{print $1}'` - tiup-cluster audit $id + tiup-cluster $client audit $id - tiup-cluster --yes start $name + tiup-cluster $client --yes start $name - tiup-cluster _test $name writable + tiup-cluster $client _test $name writable # check the data dir of tikv - tiup-cluster exec $name -N 172.19.0.102 --command "grep /home/tidb/deploy/tikv-20160/data /home/tidb/deploy/tikv-20160/scripts/run_tikv.sh" - tiup-cluster exec $name -N 172.19.0.103 --command "grep /home/tidb/my_kv_data /home/tidb/deploy/tikv-20160/scripts/run_tikv.sh" + tiup-cluster $client exec $name -N 172.19.0.102 --command "grep /home/tidb/deploy/tikv-20160/data /home/tidb/deploy/tikv-20160/scripts/run_tikv.sh" + tiup-cluster $client exec $name -N 172.19.0.103 --command "grep /home/tidb/my_kv_data /home/tidb/deploy/tikv-20160/scripts/run_tikv.sh" # test patch overwrite - tiup-cluster --yes patch $name ~/.tiup/storage/cluster/packages/tidb-$version-linux-amd64.tar.gz -R tidb --overwrite + tiup-cluster $client --yes patch $name ~/.tiup/storage/cluster/packages/tidb-$version-linux-amd64.tar.gz -R tidb --overwrite # overwrite with the same tarball twice - tiup-cluster --yes patch $name ~/.tiup/storage/cluster/packages/tidb-$version-linux-amd64.tar.gz -R tidb --overwrite + tiup-cluster $client --yes patch $name ~/.tiup/storage/cluster/packages/tidb-$version-linux-amd64.tar.gz -R tidb --overwrite - tiup-cluster --yes stop $name + tiup-cluster $client --yes stop $name - tiup-cluster --yes restart $name + tiup-cluster $client --yes restart $name - tiup-cluster _test $name writable + tiup-cluster $client _test $name writable - tiup-cluster display $name + tiup-cluster $client display $name - tiup-cluster --yes destroy $name + tiup-cluster $client --yes destroy $name } diff --git a/tests/tiup-cluster/script/scale_core.sh b/tests/tiup-cluster/script/scale_core.sh index be4544cbd7..36abba9fa2 100755 --- a/tests/tiup-cluster/script/scale_core.sh +++ b/tests/tiup-cluster/script/scale_core.sh @@ -4,29 +4,35 @@ function scale_core() { mkdir -p ~/.tiup/bin/ version=$1 + native_ssh=$2 + + client="" + if [ $native_ssh == true ]; then + client="--native-ssh" + fi name="test_scale_core_$RANDOM" topo=./topo/full_without_cdc.yaml - tiup-cluster --yes deploy $name $version $topo -i ~/.ssh/id_rsa + tiup-cluster $client --yes deploy $name $version $topo -i ~/.ssh/id_rsa - tiup-cluster list | grep "$name" + tiup-cluster $client list | grep "$name" - tiup-cluster --yes start $name + tiup-cluster $client --yes start $name - tiup-cluster _test $name writable + tiup-cluster $client _test $name writable - tiup-cluster display $name + tiup-cluster $client display $name tiup-cluster reload $name --skip-restart total_sub_one=18 echo "start scale in tidb" - tiup-cluster --yes scale-in $name -N 172.19.0.101:4000 + tiup-cluster $client --yes scale-in $name -N 172.19.0.101:4000 wait_instance_num_reach $name $total_sub_one echo "start scale out tidb" - tiup-cluster --yes scale-out $name ./topo/full_scale_in_tidb.yaml + tiup-cluster $client --yes scale-out $name ./topo/full_scale_in_tidb.yaml # echo "start scale in tikv" # tiup-cluster --yes scale-in $name -N 172.19.0.103:20160 @@ -35,10 +41,10 @@ function scale_core() { # tiup-cluster --yes scale-out $name ./topo/full_scale_in_tikv.yaml echo "start scale in pd" - tiup-cluster --yes scale-in $name -N 172.19.0.103:2379 + tiup-cluster $client --yes scale-in $name -N 172.19.0.103:2379 wait_instance_num_reach $name $total_sub_one echo "start scale out pd" - tiup-cluster --yes scale-out $name ./topo/full_scale_in_pd.yaml + tiup-cluster $client --yes scale-out $name ./topo/full_scale_in_pd.yaml - tiup-cluster _test $name writable + tiup-cluster $client _test $name writable } diff --git a/tests/tiup-cluster/script/scale_tools.sh b/tests/tiup-cluster/script/scale_tools.sh index 8928079186..40f5c5c889 100755 --- a/tests/tiup-cluster/script/scale_tools.sh +++ b/tests/tiup-cluster/script/scale_tools.sh @@ -4,45 +4,51 @@ function scale_tools() { mkdir -p ~/.tiup/bin/ version=$1 + native_ssh=$2 + + client="" + if [ $native_ssh == true ]; then + client="--native-ssh" + fi name="test_scale_tools_$RANDOM" topo=./topo/full.yaml - tiup-cluster --yes deploy $name $version $topo -i ~/.ssh/id_rsa + tiup-cluster $client --yes deploy $name $version $topo -i ~/.ssh/id_rsa - tiup-cluster list | grep "$name" + tiup-cluster $client list | grep "$name" - tiup-cluster --yes start $name + tiup-cluster $client --yes start $name - tiup-cluster _test $name writable + tiup-cluster $client _test $name writable - tiup-cluster display $name + tiup-cluster $client display $name total_sub_one=21 echo "start scale in pump" - tiup-cluster --yes scale-in $name -N 172.19.0.103:8250 + tiup-cluster $client --yes scale-in $name -N 172.19.0.103:8250 wait_instance_num_reach $name $total_sub_one echo "start scale out pump" - tiup-cluster --yes scale-out $name ./topo/full_scale_in_pump.yaml + tiup-cluster $client --yes scale-out $name ./topo/full_scale_in_pump.yaml echo "start scale in cdc" - yes | tiup-cluster scale-in $name -N 172.19.0.103:8300 + yes | tiup-cluster $client scale-in $name -N 172.19.0.103:8300 wait_instance_num_reach $name $total_sub_one echo "start scale out cdc" - yes | tiup-cluster scale-out $name ./topo/full_scale_in_cdc.yaml + yes | tiup-cluster $client scale-out $name ./topo/full_scale_in_cdc.yaml echo "start scale in tispark" - yes | tiup-cluster --yes scale-in $name -N 172.19.0.104:7078 + yes | tiup-cluster $client --yes scale-in $name -N 172.19.0.104:7078 wait_instance_num_reach $name $total_sub_one echo "start scale out tispark" - yes | tiup-cluster --yes scale-out $name ./topo/full_scale_in_tispark.yaml + yes | tiup-cluster $client --yes scale-out $name ./topo/full_scale_in_tispark.yaml echo "start scale in grafana" - tiup-cluster --yes scale-in $name -N 172.19.0.101:3000 + tiup-cluster $client --yes scale-in $name -N 172.19.0.101:3000 wait_instance_num_reach $name $total_sub_one echo "start scale out grafana" - tiup-cluster --yes scale-out $name ./topo/full_scale_in_grafana.yaml + tiup-cluster $client --yes scale-out $name ./topo/full_scale_in_grafana.yaml - tiup-cluster _test $name writable + tiup-cluster $client _test $name writable } diff --git a/tests/tiup-cluster/test_cmd.sh b/tests/tiup-cluster/test_cmd.sh index 65a2c55e87..a2ca1bd4a9 100755 --- a/tests/tiup-cluster/test_cmd.sh +++ b/tests/tiup-cluster/test_cmd.sh @@ -4,5 +4,5 @@ set -eu source script/cmd_subtest.sh -echo "test cluster for verision v4.0.2 with CDC" -cmd_subtest v4.0.2 true +echo "test cluster for verision v4.0.2 with CDC, via easy ssh" +cmd_subtest v4.0.2 true false diff --git a/tests/tiup-cluster/test_cmd_no_cdc.sh b/tests/tiup-cluster/test_cmd_no_cdc.sh index 8e366a4a24..7758940a31 100755 --- a/tests/tiup-cluster/test_cmd_no_cdc.sh +++ b/tests/tiup-cluster/test_cmd_no_cdc.sh @@ -4,5 +4,5 @@ set -eu source script/cmd_subtest.sh -echo "test cluster for verision v4.0.2 without CDC" -cmd_subtest v4.0.2 false +echo "test cluster for verision v4.0.2 without CDC, via easy ssh" +cmd_subtest v4.0.2 false false diff --git a/tests/tiup-cluster/test_cmd_with_native_ssh.sh b/tests/tiup-cluster/test_cmd_with_native_ssh.sh new file mode 100644 index 0000000000..78f721364f --- /dev/null +++ b/tests/tiup-cluster/test_cmd_with_native_ssh.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -eu + +source script/cmd_subtest.sh + +echo "test cluster for verision v4.0.2 with CDC, via native ssh" +cmd_subtest v4.0.2 true true diff --git a/tests/tiup-cluster/test_scale_core.sh b/tests/tiup-cluster/test_scale_core.sh index 8c22869645..243e06ae47 100755 --- a/tests/tiup-cluster/test_scale_core.sh +++ b/tests/tiup-cluster/test_scale_core.sh @@ -4,5 +4,5 @@ set -eu source script/scale_core.sh -echo "test scaling of core components in cluster for verision v4.0.2" -scale_core v4.0.2 true +echo "test scaling of core components in cluster for verision v4.0.2, via easy ssh" +scale_core v4.0.2 false diff --git a/tests/tiup-cluster/test_scale_core_with_native_ssh.sh b/tests/tiup-cluster/test_scale_core_with_native_ssh.sh new file mode 100644 index 0000000000..03aa062211 --- /dev/null +++ b/tests/tiup-cluster/test_scale_core_with_native_ssh.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -eu + +source script/scale_core.sh + +echo "test scaling of core components in cluster for verision v4.0.2, via native ssh" +scale_core v4.0.2 true diff --git a/tests/tiup-cluster/test_scale_tools.sh b/tests/tiup-cluster/test_scale_tools.sh index c0aadcf282..9390559837 100755 --- a/tests/tiup-cluster/test_scale_tools.sh +++ b/tests/tiup-cluster/test_scale_tools.sh @@ -4,5 +4,5 @@ set -eu source script/scale_tools.sh -echo "test scaling of tools components in cluster for verision v4.0.2" -scale_tools v4.0.2 true +echo "test scaling of tools components in cluster for verision v4.0.2, via easy ssh" +scale_tools v4.0.2 false diff --git a/tests/tiup-cluster/test_scale_tools_with_native_ssh.sh b/tests/tiup-cluster/test_scale_tools_with_native_ssh.sh new file mode 100644 index 0000000000..09e11fdf1a --- /dev/null +++ b/tests/tiup-cluster/test_scale_tools_with_native_ssh.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -eu + +source script/scale_tools.sh + +echo "test scaling of tools components in cluster for verision v4.0.2, via native ssh" +scale_tools v4.0.2 true