mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
Automatic merge from release/2.1.0 -> release/2.2.0
* commit 'dcf7c00dcb3725e7745036e7c977d6fe4e06bcd7': 添加超时处理 添加admin参数 支持ssh webconsole
This commit is contained in:
@@ -65,4 +65,13 @@ func init() {
|
||||
handleResult(args.WebConsoleOptions, ret)
|
||||
return nil
|
||||
})
|
||||
|
||||
R(&o.WebConsoleSshOptions{}, "webconsole-ssh", "Connect ssh webconsole", func(s *mcclient.ClientSession, args *o.WebConsoleSshOptions) error {
|
||||
ret, err := modules.WebConsole.DoSshConnect(s, args.IP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
handleResult(args.WebConsoleOptions, ret)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -69,3 +69,7 @@ func (m WebConsoleManager) DoK8sLogConnect(
|
||||
func (m WebConsoleManager) DoBaremetalConnect(s *mcclient.ClientSession, id string) (jsonutils.JSONObject, error) {
|
||||
return m.DoConnect(s, "baremetal", id, "", nil)
|
||||
}
|
||||
|
||||
func (m WebConsoleManager) DoSshConnect(s *mcclient.ClientSession, id string) (jsonutils.JSONObject, error) {
|
||||
return m.DoConnect(s, "ssh", id, "", nil)
|
||||
}
|
||||
|
||||
@@ -36,3 +36,8 @@ type WebConsoleBaremetalOptions struct {
|
||||
func (opt *WebConsoleBaremetalOptions) Params() (*jsonutils.JSONDict, error) {
|
||||
return StructToParams(opt)
|
||||
}
|
||||
|
||||
type WebConsoleSshOptions struct {
|
||||
WebConsoleOptions
|
||||
IP string `help:"IP to connect"`
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ type ICommand interface {
|
||||
GetProtocol() string
|
||||
GetCommand() *exec.Cmd
|
||||
Cleanup() error
|
||||
GetData(string) (isShow bool, ouput string, command string)
|
||||
ShowInfo() string
|
||||
}
|
||||
|
||||
type BaseCommand struct {
|
||||
@@ -40,6 +42,14 @@ func (c BaseCommand) GetCommand() *exec.Cmd {
|
||||
return exec.Command(c.name, c.args...)
|
||||
}
|
||||
|
||||
func (c BaseCommand) GetData(comand string) (isShow bool, ouput string, command string) {
|
||||
return true, "", ""
|
||||
}
|
||||
|
||||
func (c BaseCommand) ShowInfo() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c BaseCommand) Cleanup() error {
|
||||
log.Infof("BaseCommand Cleanup do nothing")
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/log"
|
||||
o "yunion.io/x/onecloud/pkg/webconsole/options"
|
||||
)
|
||||
|
||||
type SSHtoolSol struct {
|
||||
*BaseCommand
|
||||
IP string
|
||||
Username string
|
||||
reTry int
|
||||
showInfo string
|
||||
}
|
||||
|
||||
func NewSSHtoolSolCommand(ip string) (*SSHtoolSol, error) {
|
||||
if conn, err := net.DialTimeout("tcp", ip+":22", time.Second*2); err != nil {
|
||||
return nil, fmt.Errorf("IPAddress %s not accessable", ip)
|
||||
} else {
|
||||
conn.Close()
|
||||
return &SSHtoolSol{
|
||||
BaseCommand: nil,
|
||||
IP: ip,
|
||||
Username: "",
|
||||
reTry: 0,
|
||||
showInfo: fmt.Sprintf("%s login:", ip),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SSHtoolSol) GetCommand() *exec.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SSHtoolSol) Cleanup() error {
|
||||
log.Infof("SSHtoolSol Cleanup do nothing")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SSHtoolSol) GetProtocol() string {
|
||||
return PROTOCOL_TTY
|
||||
}
|
||||
|
||||
func (c *SSHtoolSol) GetData(data string) (isShow bool, ouput string, command string) {
|
||||
if len(c.Username) == 0 {
|
||||
if len(data) == 0 {
|
||||
//用户名不能为空
|
||||
return true, c.showInfo, ""
|
||||
}
|
||||
c.Username = data
|
||||
return false, "Password:", ""
|
||||
} else {
|
||||
return true, "", fmt.Sprintf("%s -p %s %s %s@%s", o.Options.SshpassToolPath, data, o.Options.SshToolPath, c.Username, c.IP)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SSHtoolSol) ShowInfo() string {
|
||||
c.Username = ""
|
||||
c.reTry++
|
||||
if c.reTry == 3 {
|
||||
c.reTry = 0
|
||||
//清屏
|
||||
time.Sleep(1 * time.Second)
|
||||
return "\033c " + c.showInfo
|
||||
}
|
||||
return c.showInfo
|
||||
}
|
||||
@@ -31,6 +31,7 @@ func InitHandlers(app *appsrv.Application) {
|
||||
app.AddHandler("POST", ApiPathPrefix+"k8s/<podName>/shell", auth.Authenticate(handleK8sShell))
|
||||
app.AddHandler("POST", ApiPathPrefix+"k8s/<podName>/log", auth.Authenticate(handleK8sLog))
|
||||
app.AddHandler("POST", ApiPathPrefix+"baremetal/<id>", auth.Authenticate(handleBaremetalShell))
|
||||
app.AddHandler("POST", ApiPathPrefix+"ssh/<ip>", auth.Authenticate(handleSshShell))
|
||||
}
|
||||
|
||||
func fetchEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) (map[string]string, jsonutils.JSONObject, jsonutils.JSONObject) {
|
||||
@@ -122,7 +123,7 @@ func fetchCloudEnv(ctx context.Context, w http.ResponseWriter, r *http.Request)
|
||||
if userCred == nil {
|
||||
return nil, httperrors.NewUnauthorizedError("No token founded")
|
||||
}
|
||||
s := auth.Client().NewSession(o.Options.Region, "", "internal", userCred, "")
|
||||
s := auth.Client().NewSession(o.Options.Region, "", "internal", userCred, "v2")
|
||||
return &CloudEnv{
|
||||
ClientSessin: s,
|
||||
Params: params,
|
||||
@@ -156,6 +157,20 @@ func handleK8sLog(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
handleK8sCommand(ctx, w, r, command.NewPodLogCommand)
|
||||
}
|
||||
|
||||
func handleSshShell(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
env, err := fetchCloudEnv(ctx, w, r)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
return
|
||||
}
|
||||
cmd, err := command.NewSSHtoolSolCommand(env.Params["<ip>"])
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(w, err)
|
||||
return
|
||||
}
|
||||
handleCommandSession(cmd, w)
|
||||
}
|
||||
|
||||
func handleBaremetalShell(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
env, err := fetchCloudEnv(ctx, w, r)
|
||||
if err != nil {
|
||||
|
||||
@@ -11,7 +11,9 @@ var (
|
||||
type WebConsoleOptions struct {
|
||||
cloudcommon.Options
|
||||
|
||||
ApiServer string `help:"API server url to handle websocket connection, usually with public access" default:"http://webconsole.yunion.io"`
|
||||
KubectlPath string `help:"kubectl binary path used to connect k8s cluster" default:"/usr/bin/kubectl"`
|
||||
IpmitoolPath string `help:"ipmitool binary path used to connect baremetal sol" default:"/usr/bin/ipmitool"`
|
||||
ApiServer string `help:"API server url to handle websocket connection, usually with public access" default:"http://webconsole.yunion.io"`
|
||||
KubectlPath string `help:"kubectl binary path used to connect k8s cluster" default:"/usr/bin/kubectl"`
|
||||
IpmitoolPath string `help:"ipmitool binary path used to connect baremetal sol" default:"/usr/bin/ipmitool"`
|
||||
SshToolPath string `help:"sshtool binary path used to connect server sol" default:"/usr/bin/ssh"`
|
||||
SshpassToolPath string `help:"sshpass tool binary path used to connect server sol" default:"/usr/local/bin/sshpass"`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
socketio "github.com/googollee/go-socket.io"
|
||||
"github.com/kr/pty"
|
||||
|
||||
@@ -56,19 +61,67 @@ func initSocketHandler(so socketio.Socket, p *session.Pty) {
|
||||
go func() {
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, err := p.Pty.Read(buf)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to read from pty master: %v", err)
|
||||
cleanUp(so, p)
|
||||
if p.IsOk {
|
||||
if p.Cmd == nil || p.Cmd.Process == nil {
|
||||
p.IsOk = false
|
||||
} else if p.Pty == nil {
|
||||
p.IsOk = false
|
||||
} else if n, err := p.Pty.Read(buf); err != nil {
|
||||
p.IsOk = false
|
||||
} else {
|
||||
so.Emit(OUTPUT_EVENT, string(buf[0:n]))
|
||||
}
|
||||
if !p.IsOk {
|
||||
p.Stop()
|
||||
if info := p.Session.ShowInfo(); len(info) > 0 {
|
||||
so.Emit(OUTPUT_EVENT, info)
|
||||
}
|
||||
}
|
||||
} else if p.Exit {
|
||||
return
|
||||
} else {
|
||||
//避免goroutine死循环导致主进程卡死
|
||||
time.Sleep(time.Microsecond * 50)
|
||||
}
|
||||
so.Emit(OUTPUT_EVENT, string(buf[0:n]))
|
||||
}
|
||||
}()
|
||||
|
||||
// handle write
|
||||
so.On(INPUT_EVENT, func(data string) {
|
||||
p.Pty.Write([]byte(data))
|
||||
if !p.IsOk {
|
||||
if data == "\r" {
|
||||
p.Show, p.Output, p.Command = p.Session.GetData(p.Buffer)
|
||||
so.Emit(OUTPUT_EVENT, "\r\n")
|
||||
if len(p.Output) > 0 {
|
||||
so.Emit(OUTPUT_EVENT, p.Output)
|
||||
}
|
||||
if len(p.Command) > 0 {
|
||||
log.Infof("exec: %s", p.Command)
|
||||
args := strings.Split(p.Command, " ")
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
if _pty, err := pty.Start(cmd); err != nil {
|
||||
so.Emit(OUTPUT_EVENT, err.Error()+"\r\n")
|
||||
log.Errorf("exec error: %v", err)
|
||||
} else {
|
||||
p.Pty, p.Cmd, p.IsOk = _pty, cmd, true
|
||||
}
|
||||
}
|
||||
p.Buffer, data = "", ""
|
||||
} else if data == "\u007f" {
|
||||
//退格处理
|
||||
if len(p.Buffer) > 0 {
|
||||
p.Buffer = p.Buffer[:len(p.Buffer)-1]
|
||||
data = "\b \b"
|
||||
}
|
||||
} else if strconv.IsPrint([]rune(data)[0]) {
|
||||
p.Buffer += data
|
||||
}
|
||||
if p.Show && len(data) > 0 {
|
||||
so.Emit(OUTPUT_EVENT, data)
|
||||
}
|
||||
} else {
|
||||
p.Pty.Write([]byte(data))
|
||||
}
|
||||
})
|
||||
|
||||
// handle resize
|
||||
@@ -106,4 +159,5 @@ func initSocketHandler(so socketio.Socket, p *session.Pty) {
|
||||
func cleanUp(so socketio.Socket, p *session.Pty) {
|
||||
so.Disconnect()
|
||||
p.Stop()
|
||||
p.Exit = true
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func StartService() {
|
||||
log.Fatalf("invalid --api-server %s", o.Options.ApiServer)
|
||||
}
|
||||
|
||||
for _, binPath := range []string{o.Options.KubectlPath, o.Options.IpmitoolPath} {
|
||||
for _, binPath := range []string{o.Options.KubectlPath, o.Options.IpmitoolPath, o.Options.SshToolPath, o.Options.SshpassToolPath} {
|
||||
ensureBinExists(binPath)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,12 @@ type Pty struct {
|
||||
Pty *os.File
|
||||
sizeCh chan os.Signal
|
||||
size *pty.Winsize
|
||||
Show bool
|
||||
IsOk bool
|
||||
Buffer string
|
||||
Output string
|
||||
Command string
|
||||
Exit bool
|
||||
}
|
||||
|
||||
func NewPty(session *SSession) (p *Pty, err error) {
|
||||
@@ -24,11 +30,17 @@ func NewPty(session *SSession) (p *Pty, err error) {
|
||||
p = &Pty{
|
||||
Session: session,
|
||||
Cmd: cmd,
|
||||
Show: true,
|
||||
IsOk: true,
|
||||
Exit: false,
|
||||
Pty: nil,
|
||||
}
|
||||
log.Debugf("[session %s] Start command: %#v", session.Id, cmd)
|
||||
p.Pty, err = pty.Start(p.Cmd)
|
||||
if err != nil {
|
||||
return
|
||||
if cmd != nil {
|
||||
p.Pty, err = pty.Start(p.Cmd)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
p.sizeCh = make(chan os.Signal, 1)
|
||||
p.size = &pty.Winsize{}
|
||||
@@ -40,10 +52,12 @@ func NewPty(session *SSession) (p *Pty, err error) {
|
||||
func (p *Pty) startResizeMonitor() {
|
||||
go func() {
|
||||
for range p.sizeCh {
|
||||
if err := pty.Setsize(p.Pty, p.size); err != nil {
|
||||
log.Errorf("Resize pty error: %v", err)
|
||||
} else {
|
||||
log.Debugf("Resize pty to %#v, cmd: %#v", p.size, p.Cmd)
|
||||
if p.Pty != nil {
|
||||
if err := pty.Setsize(p.Pty, p.size); err != nil {
|
||||
log.Errorf("Resize pty error: %v", err)
|
||||
} else {
|
||||
log.Debugf("Resize pty to %#v, cmd: %#v", p.size, p.Cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -55,18 +69,17 @@ func (p *Pty) Resize(size *pty.Winsize) {
|
||||
}
|
||||
|
||||
func (p *Pty) Stop() {
|
||||
var err error
|
||||
err = p.Pty.Close()
|
||||
if err != nil {
|
||||
log.Errorf("Close PTY error: %v", err)
|
||||
if p.Pty != nil {
|
||||
if err := p.Pty.Close(); err != nil {
|
||||
log.Errorf("Close PTY error: %v", err)
|
||||
}
|
||||
}
|
||||
err = p.Cmd.Process.Signal(os.Kill)
|
||||
if err != nil {
|
||||
log.Errorf("Kill command process error: %v", err)
|
||||
}
|
||||
err = p.Cmd.Wait()
|
||||
if err != nil {
|
||||
log.Errorf("Wait command error: %v", err)
|
||||
if p.Cmd != nil && p.Cmd.Process != nil {
|
||||
if err := p.Cmd.Process.Signal(os.Kill); err != nil {
|
||||
log.Errorf("Kill command process error: %v", err)
|
||||
} else if err := p.Cmd.Wait(); err != nil {
|
||||
log.Errorf("Wait command error: %v", err)
|
||||
}
|
||||
}
|
||||
p.Session.Close()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user