From 94a5eedae4bf55055873de3d2f6b329ec7452963 Mon Sep 17 00:00:00 2001 From: Zexi Li Date: Wed, 5 Sep 2018 18:14:08 +0800 Subject: [PATCH] webconsole: support baremetal ipmitool sol --- cmd/climc/shell/webconsole.go | 19 +++++-- pkg/mcclient/modules/mod_webconsole.go | 4 ++ pkg/mcclient/options/webconsole.go | 15 ++++-- pkg/webconsole/command/ipmi_command.go | 49 +++++++++++++++++ pkg/webconsole/handlers.go | 73 +++++++++++++++++++++++--- pkg/webconsole/session/pty_session.go | 1 + 6 files changed, 145 insertions(+), 16 deletions(-) create mode 100644 pkg/webconsole/command/ipmi_command.go diff --git a/cmd/climc/shell/webconsole.go b/cmd/climc/shell/webconsole.go index 6007362c47..0a784dc555 100644 --- a/cmd/climc/shell/webconsole.go +++ b/cmd/climc/shell/webconsole.go @@ -12,12 +12,12 @@ import ( ) func init() { - handleResult := func(opt o.WebConsoleFrontendOptions, obj jsonutils.JSONObject) error { - if opt.WebUrl == "" { + handleResult := func(opt o.WebConsoleOptions, obj jsonutils.JSONObject) error { + if opt.WebconsoleUrl == "" { printObject(obj) return nil } - u, err := url.Parse(opt.WebUrl) + u, err := url.Parse(opt.WebconsoleUrl) if err != nil { return err } @@ -40,7 +40,7 @@ func init() { if err != nil { return err } - handleResult(args.WebConsoleFrontendOptions, ret) + handleResult(args.WebConsoleOptions, ret) return nil }) @@ -53,7 +53,16 @@ func init() { if err != nil { return err } - handleResult(args.WebConsoleFrontendOptions, ret) + handleResult(args.WebConsoleOptions, ret) + return nil + }) + + R(&o.WebConsoleBaremetalOptions{}, "webconsole-baremetal", "Connect baremetal host webconsole", func(s *mcclient.ClientSession, args *o.WebConsoleBaremetalOptions) error { + ret, err := modules.WebConsole.DoBaremetalConnect(s, args.ID) + if err != nil { + return err + } + handleResult(args.WebConsoleOptions, ret) return nil }) } diff --git a/pkg/mcclient/modules/mod_webconsole.go b/pkg/mcclient/modules/mod_webconsole.go index 6ff9db80ab..52a145384a 100644 --- a/pkg/mcclient/modules/mod_webconsole.go +++ b/pkg/mcclient/modules/mod_webconsole.go @@ -63,3 +63,7 @@ func (m WebConsoleManager) DoK8sLogConnect( ) (jsonutils.JSONObject, error) { return m.DoK8sConnect(s, id, "log", params) } + +func (m WebConsoleManager) DoBaremetalConnect(s *mcclient.ClientSession, id string) (jsonutils.JSONObject, error) { + return m.DoConnect(s, "baremetal", id, "", nil) +} diff --git a/pkg/mcclient/options/webconsole.go b/pkg/mcclient/options/webconsole.go index ce5cfb06c9..723c03da6f 100644 --- a/pkg/mcclient/options/webconsole.go +++ b/pkg/mcclient/options/webconsole.go @@ -4,12 +4,12 @@ import ( "yunion.io/x/jsonutils" ) -type WebConsoleFrontendOptions struct { - WebUrl string `help:"Frontend terminal HTML web url" short-token:"w"` +type WebConsoleOptions struct { + WebconsoleUrl string `help:"Frontend webconsole url" short-token:"w" default:"$WEBCONSOLE_URL"` } type PodBaseOptions struct { - WebConsoleFrontendOptions + WebConsoleOptions NAME string `help:"Name of k8s pod to connect"` Namespace string `help:"Namespace of this pod"` Container string `help:"Container in this pod"` @@ -27,3 +27,12 @@ type PodShellOptions struct { type PodLogOptoins struct { PodBaseOptions } + +type WebConsoleBaremetalOptions struct { + WebConsoleOptions + ID string `help:"Baremetal host id or name"` +} + +func (opt *WebConsoleBaremetalOptions) Params() (*jsonutils.JSONDict, error) { + return StructToParams(opt) +} diff --git a/pkg/webconsole/command/ipmi_command.go b/pkg/webconsole/command/ipmi_command.go new file mode 100644 index 0000000000..94e656b754 --- /dev/null +++ b/pkg/webconsole/command/ipmi_command.go @@ -0,0 +1,49 @@ +package command + +import ( + "fmt" + "os/exec" +) + +type IpmiInfo struct { + IpAddr string `json:"ip_addr"` + Username string `json:"username"` + Password string `json:"password"` + Present bool `json:"present"` +} + +type IpmitoolSol struct { + *BaseCommand + Info *IpmiInfo +} + +func NewIpmitoolSolCommand(info *IpmiInfo) (*IpmitoolSol, error) { + if info.IpAddr == "" { + return nil, fmt.Errorf("Empty host ip address") + } + if info.Username == "" { + return nil, fmt.Errorf("Empty username") + } + if info.Password == "" { + return nil, fmt.Errorf("Empty password") + } + name := "ipmitool" + cmd := NewBaseCommand(name, "-I", "lanplus") + cmd.AppendArgs("-H", info.IpAddr) + cmd.AppendArgs("-U", info.Username) + cmd.AppendArgs("-P", info.Password) + cmd.AppendArgs("sol", "activate") + tool := &IpmitoolSol{ + BaseCommand: cmd, + Info: info, + } + return tool, nil +} + +func (c *IpmitoolSol) GetCommand() *exec.Cmd { + return c.BaseCommand.GetCommand() +} + +func (c IpmitoolSol) GetProtocol() string { + return PROTOCOL_TTY +} diff --git a/pkg/webconsole/handlers.go b/pkg/webconsole/handlers.go index aebe822773..cd44f4f0c0 100644 --- a/pkg/webconsole/handlers.go +++ b/pkg/webconsole/handlers.go @@ -13,7 +13,9 @@ import ( "yunion.io/x/onecloud/pkg/appctx" "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules" "yunion.io/x/onecloud/pkg/mcclient/modules/k8s" "yunion.io/x/onecloud/pkg/webconsole/command" o "yunion.io/x/onecloud/pkg/webconsole/options" @@ -28,6 +30,7 @@ const ( func InitHandlers(app *appsrv.Application) { app.AddHandler("POST", ApiPathPrefix+"k8s//shell", auth.Authenticate(handleK8sShell)) app.AddHandler("POST", ApiPathPrefix+"k8s//log", auth.Authenticate(handleK8sLog)) + app.AddHandler("POST", ApiPathPrefix+"baremetal/", auth.Authenticate(handleBaremetalShell)) } func fetchEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) (map[string]string, jsonutils.JSONObject, jsonutils.JSONObject) { @@ -105,6 +108,30 @@ func fetchK8sEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) (* }, nil } +type CloudEnv struct { + ClientSessin *mcclient.ClientSession + Params map[string]string + Query jsonutils.JSONObject + Body jsonutils.JSONObject + Ctx context.Context +} + +func fetchCloudEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) (*CloudEnv, error) { + params, query, body := fetchEnv(ctx, w, r) + userCred := auth.FetchUserCredential(ctx) + if userCred == nil { + return nil, httperrors.NewUnauthorizedError("No token founded") + } + s := auth.Client().NewSession(o.Options.Region, "", "internal", userCred, "") + return &CloudEnv{ + ClientSessin: s, + Params: params, + Query: query, + Body: body, + Ctx: ctx, + }, nil +} + func handleK8sCommand( ctx context.Context, w http.ResponseWriter, @@ -118,6 +145,44 @@ func handleK8sCommand( } cmd := cmdFactory(env.KubeConfig, env.Namespace, env.Pod, env.Container) + handleCommandSession(cmd, w) +} + +func handleK8sShell(ctx context.Context, w http.ResponseWriter, r *http.Request) { + handleK8sCommand(ctx, w, r, command.NewPodBashCommand) +} + +func handleK8sLog(ctx context.Context, w http.ResponseWriter, r *http.Request) { + handleK8sCommand(ctx, w, r, command.NewPodLogCommand) +} + +func handleBaremetalShell(ctx context.Context, w http.ResponseWriter, r *http.Request) { + env, err := fetchCloudEnv(ctx, w, r) + if err != nil { + httperrors.GeneralServerError(w, err) + return + } + hostId := env.Params[""] + ret, err := modules.Hosts.GetSpecific(env.ClientSessin, hostId, "ipmi", nil) + if err != nil { + httperrors.GeneralServerError(w, err) + return + } + info := command.IpmiInfo{} + err = ret.Unmarshal(&info) + if err != nil { + httperrors.GeneralServerError(w, err) + return + } + cmd, err := command.NewIpmitoolSolCommand(&info) + if err != nil { + httperrors.GeneralServerError(w, err) + return + } + handleCommandSession(cmd, w) +} + +func handleCommandSession(cmd command.ICommand, w http.ResponseWriter) { cmdSession, err := session.Manager.Save(cmd) if err != nil { httperrors.GeneralServerError(w, err) @@ -134,14 +199,6 @@ func handleK8sCommand( sendJSON(w, data) } -func handleK8sShell(ctx context.Context, w http.ResponseWriter, r *http.Request) { - handleK8sCommand(ctx, w, r, command.NewPodBashCommand) -} - -func handleK8sLog(ctx context.Context, w http.ResponseWriter, r *http.Request) { - handleK8sCommand(ctx, w, r, command.NewPodLogCommand) -} - func sendJSON(w http.ResponseWriter, body jsonutils.JSONObject) { ret := jsonutils.NewDict() if body != nil { diff --git a/pkg/webconsole/session/pty_session.go b/pkg/webconsole/session/pty_session.go index c77f7f5600..c28d0e6542 100644 --- a/pkg/webconsole/session/pty_session.go +++ b/pkg/webconsole/session/pty_session.go @@ -25,6 +25,7 @@ func NewPty(session *SSession) (p *Pty, err error) { Session: session, Cmd: cmd, } + log.Debugf("[session %s] Start command: %#v", session.Id, cmd) p.Pty, err = pty.Start(p.Cmd) if err != nil { return