diff --git a/cmd/climc/shell/compute/servers.go b/cmd/climc/shell/compute/servers.go index 4c191fa4b8..55974dc600 100644 --- a/cmd/climc/shell/compute/servers.go +++ b/cmd/climc/shell/compute/servers.go @@ -104,6 +104,7 @@ func init() { cmd.Perform("cpuset-remove", &options.ServerIdOptions{}) cmd.Perform("calculate-record-checksum", &options.ServerIdOptions{}) cmd.Perform("set-class-metadata", &baseoptions.ResourceMetadataOptions{}) + cmd.Perform("monitor", &options.ServerMonitorOptions{}) cmd.Get("vnc", new(options.ServerIdOptions)) cmd.Get("desc", new(options.ServerIdOptions)) @@ -305,23 +306,6 @@ func init() { return nil }) - R(&options.ServerMonitorOptions{}, "server-monitor", "Send commands to qemu monitor", func(s *mcclient.ClientSession, opts *options.ServerMonitorOptions) error { - params, err := baseoptions.StructToParams(opts) - if err != nil { - return err - } - ret, err := modules.Servers.PerformAction(s, opts.ID, "monitor", params) - if err != nil { - return err - } - result, err := ret.GetString("results") - if err != nil { - return err - } - fmt.Println(result) - return nil - }) - type ServerDiskSnapshotOptions struct { SERVER string `help:"server ID or Name"` DISK string `help:"create snapshot disk id"` diff --git a/pkg/apis/compute/guests.go b/pkg/apis/compute/guests.go index 203dc43f8a..c46ecbe109 100644 --- a/pkg/apis/compute/guests.go +++ b/pkg/apis/compute/guests.go @@ -840,6 +840,11 @@ type ServerGetCPUSetCoresResp struct { HostUsedCores []int `json:"host_used_cores"` } +type ServerMonitorInput struct { + COMMAND string + QMP bool +} + type ServerQemuInfo struct { Version string `json:"version"` Cmdline string `json:"cmdline"` diff --git a/pkg/compute/guestdrivers/kvm.go b/pkg/compute/guestdrivers/kvm.go index b839486b42..f145e111f9 100644 --- a/pkg/compute/guestdrivers/kvm.go +++ b/pkg/compute/guestdrivers/kvm.go @@ -422,7 +422,10 @@ func (self *SKVMGuestDriver) RequestChangeVmConfig(ctx context.Context, guest *m } func (self *SKVMGuestDriver) RequestSoftReset(ctx context.Context, guest *models.SGuest, task taskman.ITask) error { - _, err := guest.SendMonitorCommand(ctx, task.GetUserCred(), "system_reset") + _, err := guest.SendMonitorCommand( + ctx, task.GetUserCred(), + &api.ServerMonitorInput{COMMAND: "system_reset"}, + ) return err } diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index 22e85de210..bd8c603aac 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -115,14 +115,13 @@ func (self *SGuest) PerformMonitor( ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, - data jsonutils.JSONObject, + input *api.ServerMonitorInput, ) (jsonutils.JSONObject, error) { if utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_BLOCK_STREAM, api.VM_MIGRATING}) { - cmd, err := data.GetString("command") - if err != nil { + if input.COMMAND == "" { return nil, httperrors.NewMissingParameterError("command") } - return self.SendMonitorCommand(ctx, userCred, cmd) + return self.SendMonitorCommand(ctx, userCred, input) } return nil, httperrors.NewInvalidStatusError("Cannot send command in status %s", self.Status) } @@ -2927,7 +2926,7 @@ func (self *SGuest) PerformSendkeys(ctx context.Context, userCred mcclient.Token if err == nil { cmd = fmt.Sprintf("%s %d", cmd, duration) } - _, err = self.SendMonitorCommand(ctx, userCred, cmd) + _, err = self.SendMonitorCommand(ctx, userCred, &api.ServerMonitorInput{COMMAND: cmd}) return nil, err } @@ -2962,13 +2961,14 @@ func (self *SGuest) IsLegalKey(key string) bool { return true } -func (self *SGuest) SendMonitorCommand(ctx context.Context, userCred mcclient.TokenCredential, cmd string) (jsonutils.JSONObject, error) { +func (self *SGuest) SendMonitorCommand(ctx context.Context, userCred mcclient.TokenCredential, cmd *api.ServerMonitorInput) (jsonutils.JSONObject, error) { host, _ := self.GetHost() url := fmt.Sprintf("%s/servers/%s/monitor", host.ManagerUri, self.Id) header := http.Header{} header.Add("X-Auth-Token", userCred.GetTokenString()) body := jsonutils.NewDict() - body.Add(jsonutils.NewString(cmd), "cmd") + body.Add(jsonutils.NewString(cmd.COMMAND), "cmd") + body.Add(jsonutils.NewBool(cmd.QMP), "qmp") _, res, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, body, false) if err != nil { return nil, err diff --git a/pkg/hostman/guestman/guesthandlers/guesthandler.go b/pkg/hostman/guestman/guesthandlers/guesthandler.go index cbfce798ce..32dab2c938 100644 --- a/pkg/hostman/guestman/guesthandlers/guesthandler.go +++ b/pkg/hostman/guestman/guesthandlers/guesthandler.go @@ -230,7 +230,8 @@ func guestMonitor(ctx context.Context, userCred mcclient.TokenCredential, sid st c <- res } cmd, _ := body.GetString("cmd") - err := guestman.GetGuestManager().Monitor(sid, cmd, cb) + qmp := jsonutils.QueryBoolean(body, "qmp", false) + err := guestman.GetGuestManager().Monitor(sid, cmd, qmp, cb) if err != nil { return nil, err } else { diff --git a/pkg/hostman/guestman/guestman.go b/pkg/hostman/guestman/guestman.go index 65a2637e8c..ccf8d72534 100644 --- a/pkg/hostman/guestman/guestman.go +++ b/pkg/hostman/guestman/guestman.go @@ -396,13 +396,19 @@ func (m *SGuestManager) PrepareDeploy(sid string) error { return nil } -func (m *SGuestManager) Monitor(sid, cmd string, callback func(string)) error { +func (m *SGuestManager) Monitor(sid, cmd string, qmp bool, callback func(string)) error { if guest, ok := m.GetServer(sid); ok { if guest.IsRunning() { if guest.Monitor == nil { return httperrors.NewBadRequestError("Monitor disconnected??") } - guest.Monitor.HumanMonitorCommand(cmd, callback) + if qmp { + if err := guest.Monitor.QemuMonitorCommand(cmd, callback); err != nil { + return errors.Wrap(err, "qemu monitor command") + } + } else { + guest.Monitor.HumanMonitorCommand(cmd, callback) + } return nil } else { return httperrors.NewBadRequestError("Server stopped??") diff --git a/pkg/hostman/monitor/monitor.go b/pkg/hostman/monitor/monitor.go index afde603d07..1d9623887f 100644 --- a/pkg/hostman/monitor/monitor.go +++ b/pkg/hostman/monitor/monitor.go @@ -150,6 +150,7 @@ type Monitor interface { // The callback function will be called in another goroutine SimpleCommand(cmd string, callback StringCallback) HumanMonitorCommand(cmd string, callback StringCallback) + QemuMonitorCommand(cmd string, callback StringCallback) error QueryStatus(StringCallback) GetVersion(StringCallback) @@ -269,6 +270,10 @@ func (m *SBaseMonitor) IsConnected() bool { return m.connected } +func (m *SBaseMonitor) QemuMonitorCommand(cmd string, callback StringCallback) error { + return errors.ErrNotSupported +} + func (m *SBaseMonitor) checkReading() bool { m.mutex.Lock() defer m.mutex.Unlock() diff --git a/pkg/hostman/monitor/qmp.go b/pkg/hostman/monitor/qmp.go index 62e6327fbe..e52a2b6f52 100644 --- a/pkg/hostman/monitor/qmp.go +++ b/pkg/hostman/monitor/qmp.go @@ -348,6 +348,25 @@ func (m *QmpMonitor) SimpleCommand(cmd string, callback StringCallback) { m.Query(c, cb) } +func (m *QmpMonitor) QemuMonitorCommand(rawCmd string, callback StringCallback) error { + c := Command{} + if err := json.Unmarshal([]byte(rawCmd), &c); err != nil { + return errors.Errorf("unsupport command format: %s", err) + } + + cb := func(res *Response) { + log.Debugf("Monitor %s ret: %s", m.server, res.Return) + if res.ErrorVal != nil { + callback(res.ErrorVal.Error()) + } else { + callback(strings.Trim(string(res.Return), `""`)) + } + } + + m.Query(&c, cb) + return nil +} + func (m *QmpMonitor) HumanMonitorCommand(cmd string, callback StringCallback) { var ( c = &Command{ diff --git a/pkg/mcclient/options/compute/servers.go b/pkg/mcclient/options/compute/servers.go index b713ba6b23..d7275f2818 100644 --- a/pkg/mcclient/options/compute/servers.go +++ b/pkg/mcclient/options/compute/servers.go @@ -779,10 +779,14 @@ func (o *ServerSendKeyOptions) Description() string { } type ServerMonitorOptions struct { - ID string `help:"ID or Name of server" json:"-"` + ServerIdOptions + Qmp bool `help:"Use qmp protocol, default is hmp"` COMMAND string `help:"Qemu Monitor command to send"` - Admin *bool `help:"Is this an admin call?"` +} + +func (o *ServerMonitorOptions) Params() (jsonutils.JSONObject, error) { + return options.StructToParams(o) } type ServerSaveImageOptions struct {