mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-05-20 01:18:49 +08:00
1132 lines
27 KiB
Go
1132 lines
27 KiB
Go
// Copyright 2019 Yunion
|
|
//
|
|
// 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,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package monitor
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"regexp"
|
|
"runtime/debug"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/net/context"
|
|
|
|
"yunion.io/x/jsonutils"
|
|
"yunion.io/x/log"
|
|
"yunion.io/x/pkg/errors"
|
|
"yunion.io/x/pkg/utils"
|
|
|
|
"yunion.io/x/onecloud/pkg/hostman/hostutils"
|
|
)
|
|
|
|
// https://github.com/qemu/qemu/blob/master/docs/interop/qmp-spec.txt
|
|
// https://wiki.qemu.org/QMP
|
|
/*
|
|
Not support oob yet
|
|
1. response error message
|
|
{ "error": { "class": json-string, "desc": json-string }, "id": json-value }
|
|
2. response event message
|
|
{ "event": json-string, "data": json-object,
|
|
"timestamp": { "seconds": json-number, "microseconds": json-number } }
|
|
3. response cmd result
|
|
{ "return": json-value, "id": json-value }
|
|
4. response qmp init information
|
|
{ "QMP": {"version": {"qemu": {"micro": 0, "minor": 0, "major": 3},
|
|
"package": "v3.0.0"}, "capabilities": [] } }
|
|
*/
|
|
|
|
var ignoreEvents = []string{`"RTC_CHANGE"`}
|
|
|
|
type qmpMonitorCallBack func(*Response)
|
|
type qmpEventCallback func(*Event)
|
|
|
|
type Response struct {
|
|
Return []byte
|
|
ErrorVal *Error
|
|
Id string
|
|
}
|
|
|
|
type Event struct {
|
|
Event string `json:"event"`
|
|
Data map[string]interface{} `json:"data"`
|
|
Timestamp *Timestamp `json:"timestamp"`
|
|
}
|
|
|
|
func (e *Event) String() string {
|
|
return fmt.Sprintf("QMP Event result: %#v", e)
|
|
}
|
|
|
|
type Timestamp struct {
|
|
Seconds int64 `json:"seconds"`
|
|
Microsenconds int64 `json:"microsenconds"`
|
|
}
|
|
|
|
type Command struct {
|
|
Execute string `json:"execute"`
|
|
Args interface{} `json:"arguments,omitempty"`
|
|
}
|
|
|
|
type NetworkModify struct {
|
|
Device string `json:"device"`
|
|
Ipmask string `json:"ipmask"`
|
|
Gateway string `json:"gateway"`
|
|
}
|
|
|
|
type Version struct {
|
|
Package string `json:"package"`
|
|
QEMU struct {
|
|
Major int `json:"major"`
|
|
Micro int `json:"micro"`
|
|
Minor int `json:"minor"`
|
|
} `json:"qemu"`
|
|
}
|
|
|
|
func (v *Version) String() string {
|
|
q := v.QEMU
|
|
return fmt.Sprintf("%d.%d.%d", q.Major, q.Minor, q.Micro)
|
|
}
|
|
|
|
type Error struct {
|
|
Class string `json:"class"`
|
|
Desc string `json:"desc"`
|
|
}
|
|
|
|
func (e *Error) Error() string {
|
|
return fmt.Sprintf("%s: %s", e.Class, e.Desc)
|
|
}
|
|
|
|
type QmpMonitor struct {
|
|
SBaseMonitor
|
|
|
|
qmpEventFunc qmpEventCallback
|
|
commandQueue []*Command
|
|
callbackQueue []qmpMonitorCallBack
|
|
jobs map[string]BlockJob
|
|
}
|
|
|
|
func NewQmpMonitor(server, sid string, OnMonitorDisConnect, OnMonitorTimeout MonitorErrorFunc,
|
|
OnMonitorConnected MonitorSuccFunc, qmpEventFunc qmpEventCallback) *QmpMonitor {
|
|
m := &QmpMonitor{
|
|
SBaseMonitor: *NewBaseMonitor(server, sid, OnMonitorConnected, OnMonitorDisConnect, OnMonitorTimeout),
|
|
qmpEventFunc: qmpEventFunc,
|
|
commandQueue: make([]*Command, 0),
|
|
callbackQueue: make([]qmpMonitorCallBack, 0),
|
|
jobs: map[string]BlockJob{},
|
|
}
|
|
|
|
// On qmp init must set capabilities
|
|
m.commandQueue = append(m.commandQueue, &Command{Execute: "qmp_capabilities"})
|
|
m.callbackQueue = append(m.callbackQueue, nil)
|
|
|
|
return m
|
|
}
|
|
|
|
func (m *QmpMonitor) actionResult(res *Response) string {
|
|
if res.ErrorVal != nil {
|
|
log.Errorf("Qmp Monitor action result %s: %s", m.server, res.ErrorVal.Error())
|
|
return res.ErrorVal.Error()
|
|
} else {
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func (m *QmpMonitor) callBack(res *Response) {
|
|
m.mutex.Lock()
|
|
if len(m.callbackQueue) == 0 {
|
|
m.mutex.Unlock()
|
|
return
|
|
}
|
|
cb := m.callbackQueue[0]
|
|
m.callbackQueue = m.callbackQueue[1:]
|
|
m.mutex.Unlock()
|
|
if cb != nil {
|
|
go func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
log.Errorf("PANIC %s %s:\n%s", m.server, debug.Stack(), r)
|
|
}
|
|
}()
|
|
cb(res)
|
|
}()
|
|
}
|
|
}
|
|
|
|
func (m *QmpMonitor) read(r io.Reader) {
|
|
if !m.checkReading() {
|
|
return
|
|
}
|
|
scanner := bufio.NewScanner(r)
|
|
for scanner.Scan() {
|
|
var objmap map[string]*json.RawMessage
|
|
b := scanner.Bytes()
|
|
if err := json.Unmarshal(b, &objmap); err != nil {
|
|
log.Errorf("Unmarshal %s error: %s", m.server, err.Error())
|
|
continue
|
|
}
|
|
if val, ok := objmap["event"]; !ok || !utils.IsInStringArray(string(*val), ignoreEvents) {
|
|
log.Infof("QMP Read %s: %s", m.server, string(b))
|
|
}
|
|
if val, ok := objmap["error"]; ok {
|
|
var res = &Response{}
|
|
res.ErrorVal = &Error{}
|
|
json.Unmarshal(*val, res.ErrorVal)
|
|
if id, ok := objmap["id"]; ok {
|
|
res.Id = string(*id)
|
|
}
|
|
m.callBack(res)
|
|
} else if val, ok := objmap["return"]; ok {
|
|
var res = &Response{}
|
|
res.Return = []byte(*val)
|
|
if id, ok := objmap["id"]; ok {
|
|
res.Id = string(*id)
|
|
}
|
|
m.callBack(res)
|
|
} else if val, ok := objmap["event"]; ok {
|
|
var event = &Event{
|
|
Event: string(*val),
|
|
Data: make(map[string]interface{}, 0),
|
|
Timestamp: new(Timestamp),
|
|
}
|
|
if data, ok := objmap["data"]; ok {
|
|
json.Unmarshal(*data, &event.Data)
|
|
}
|
|
if timestamp, ok := objmap["timestamp"]; ok {
|
|
json.Unmarshal(*timestamp, event.Timestamp)
|
|
}
|
|
m.watchEvent(event)
|
|
} else if val, ok := objmap["QMP"]; ok {
|
|
// On qmp connected
|
|
json.Unmarshal(*val, &objmap)
|
|
if val, ok = objmap["version"]; ok {
|
|
var version Version
|
|
json.Unmarshal(*val, &version)
|
|
m.QemuVersion = version.String()
|
|
}
|
|
|
|
// remove reader timeout
|
|
m.rwc.SetReadDeadline(time.Time{})
|
|
m.connected = true
|
|
m.timeout = false
|
|
go m.query()
|
|
if m.OnMonitorConnected != nil {
|
|
go m.OnMonitorConnected()
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Infof("Scan over %s ...", m.server)
|
|
err := scanner.Err()
|
|
if err != nil {
|
|
log.Infof("QMP Disconnected %s: %s", m.server, err)
|
|
}
|
|
if m.timeout {
|
|
if m.OnMonitorTimeout != nil {
|
|
m.OnMonitorTimeout(err)
|
|
}
|
|
} else if m.connected {
|
|
m.connected = false
|
|
if m.OnMonitorDisConnect != nil {
|
|
m.OnMonitorDisConnect(err)
|
|
}
|
|
}
|
|
m.reading = false
|
|
}
|
|
|
|
func (m *QmpMonitor) watchEvent(event *Event) {
|
|
if !utils.IsInStringArray(event.Event, ignoreEvents) {
|
|
log.Infof("QMP event %s: %s", m.server, event.String())
|
|
}
|
|
if m.qmpEventFunc != nil {
|
|
go m.qmpEventFunc(event)
|
|
}
|
|
}
|
|
|
|
func (m *QmpMonitor) write(cmd []byte) error {
|
|
log.Infof("QMP Write %s: %s", m.server, string(cmd))
|
|
length, index := len(cmd), 0
|
|
for index < length {
|
|
i, err := m.rwc.Write(cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
index += i
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *QmpMonitor) query() {
|
|
if !m.checkWriting() {
|
|
return
|
|
}
|
|
for {
|
|
if len(m.commandQueue) == 0 {
|
|
break
|
|
}
|
|
|
|
// pop cmd
|
|
m.mutex.Lock()
|
|
cmd := m.commandQueue[0]
|
|
m.commandQueue = m.commandQueue[1:]
|
|
|
|
c, _ := json.Marshal(cmd)
|
|
err := m.write(c)
|
|
m.mutex.Unlock()
|
|
if err != nil {
|
|
log.Errorf("Write %s to monitor %s error: %s", c, m.server, err)
|
|
break
|
|
}
|
|
}
|
|
m.writing = false
|
|
}
|
|
|
|
func (m *QmpMonitor) Query(cmd *Command, cb qmpMonitorCallBack) {
|
|
// push cmd
|
|
m.mutex.Lock()
|
|
m.commandQueue = append(m.commandQueue, cmd)
|
|
m.callbackQueue = append(m.callbackQueue, cb)
|
|
m.mutex.Unlock()
|
|
|
|
if m.connected {
|
|
if !m.writing {
|
|
go m.query()
|
|
}
|
|
if !m.reading {
|
|
go m.read(m.rwc)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *QmpMonitor) ConnectWithSocket(address string) error {
|
|
err := m.SBaseMonitor.connect("unix", address)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
go m.read(m.rwc)
|
|
return nil
|
|
}
|
|
|
|
func (m *QmpMonitor) Connect(host string, port int) error {
|
|
err := m.SBaseMonitor.connect("tcp", fmt.Sprintf("%s:%d", host, port))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
go m.read(m.rwc)
|
|
return nil
|
|
}
|
|
|
|
func (m *QmpMonitor) parseCmd(cmd string) string {
|
|
re := regexp.MustCompile(`\s+`)
|
|
parts := re.Split(strings.TrimSpace(cmd), -1)
|
|
if parts[0] == "info" && len(parts) > 1 {
|
|
return "query-" + parts[1]
|
|
} else {
|
|
return parts[0]
|
|
}
|
|
}
|
|
|
|
func (m *QmpMonitor) SimpleCommand(cmd string, callback StringCallback) {
|
|
cmd = m.parseCmd(cmd)
|
|
var cb func(res *Response)
|
|
if callback != nil {
|
|
cb = func(res *Response) {
|
|
if res.ErrorVal != nil {
|
|
callback(res.ErrorVal.Error())
|
|
} else {
|
|
callback(string(res.Return))
|
|
}
|
|
}
|
|
}
|
|
c := &Command{Execute: cmd}
|
|
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{
|
|
Execute: "human-monitor-command",
|
|
Args: map[string]string{"command-line": cmd},
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
func (m *QmpMonitor) QueryStatus(callback StringCallback) {
|
|
m.HumanMonitorCommand("info status", m.parseStatus(callback))
|
|
}
|
|
|
|
// func (m *QmpMonitor) parseStatus(callback StringCallback) qmpMonitorCallBack {
|
|
// return func(res *Response) {
|
|
// if res.ErrorVal != nil {
|
|
// callback("unknown")
|
|
// return
|
|
// }
|
|
// var val map[string]interface{}
|
|
// err := json.Unmarshal(res.Return, &val)
|
|
// if err != nil {
|
|
// callback("unknown")
|
|
// return
|
|
// }
|
|
// if status, ok := val["status"]; !ok {
|
|
// callback("unknown")
|
|
// } else {
|
|
// str, _ := status.(string)
|
|
// callback(str)
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// If get version error, callback with empty string
|
|
func (m *QmpMonitor) GetVersion(callback StringCallback) {
|
|
cmd := &Command{Execute: "query-version"}
|
|
m.Query(cmd, m.parseVersion(callback))
|
|
}
|
|
|
|
func (m *QmpMonitor) parseVersion(callback StringCallback) qmpMonitorCallBack {
|
|
return func(res *Response) {
|
|
if res.ErrorVal != nil {
|
|
callback("")
|
|
return
|
|
}
|
|
var version Version
|
|
err := json.Unmarshal(res.Return, &version)
|
|
if err != nil {
|
|
callback("")
|
|
} else {
|
|
callback(version.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *QmpMonitor) GetBlocks(callback func([]QemuBlock)) {
|
|
var cb = func(res *Response) {
|
|
if res.ErrorVal != nil {
|
|
log.Errorf("GetBlocks error %s", res.ErrorVal)
|
|
callback(nil)
|
|
return
|
|
}
|
|
jr, err := jsonutils.Parse(res.Return)
|
|
if err != nil {
|
|
log.Errorf("Get %s block error %s", m.server, err)
|
|
callback(nil)
|
|
return
|
|
}
|
|
blocks := []QemuBlock{}
|
|
jr.Unmarshal(&blocks)
|
|
callback(blocks)
|
|
}
|
|
|
|
cmd := &Command{Execute: "query-block"}
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) ChangeCdrom(dev string, path string, callback StringCallback) {
|
|
m.HumanMonitorCommand(fmt.Sprintf("change %s %s", dev, path), callback)
|
|
}
|
|
|
|
func (m *QmpMonitor) EjectCdrom(dev string, callback StringCallback) {
|
|
m.HumanMonitorCommand(fmt.Sprintf("eject -f %s", dev), callback)
|
|
}
|
|
|
|
func (m *QmpMonitor) DriveDel(idstr string, callback StringCallback) {
|
|
m.HumanMonitorCommand(fmt.Sprintf("drive_del %s", idstr), callback)
|
|
}
|
|
|
|
func (m *QmpMonitor) DeviceDel(idstr string, callback StringCallback) {
|
|
//m.HumanMonitorCommand(fmt.Sprintf("device_del %s", idstr), callback)
|
|
var (
|
|
args = map[string]interface{}{
|
|
"id": idstr,
|
|
}
|
|
cmd = &Command{
|
|
Execute: "device_del",
|
|
Args: args,
|
|
}
|
|
|
|
cb = func(res *Response) {
|
|
callback(m.actionResult(res))
|
|
}
|
|
)
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) ObjectDel(idstr string, callback StringCallback) {
|
|
m.HumanMonitorCommand(fmt.Sprintf("object_del %s", idstr), callback)
|
|
}
|
|
|
|
func (m *QmpMonitor) XBlockdevChange(parent, node, child string, callback StringCallback) {
|
|
cb := func(res *Response) {
|
|
callback(m.actionResult(res))
|
|
}
|
|
cmd := &Command{
|
|
Execute: "x-blockdev-change",
|
|
}
|
|
args := map[string]interface{}{
|
|
"parent": parent,
|
|
}
|
|
if len(node) > 0 {
|
|
args["node"] = node
|
|
}
|
|
if len(child) > 0 {
|
|
args["child"] = child
|
|
}
|
|
cmd.Args = args
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) DriveAdd(bus, node string, params map[string]string, callback StringCallback) {
|
|
var paramsKvs = []string{}
|
|
for k, v := range params {
|
|
paramsKvs = append(paramsKvs, fmt.Sprintf("%s=%s", k, v))
|
|
}
|
|
cmd := "drive_add"
|
|
if len(node) > 0 {
|
|
cmd = fmt.Sprintf("drive_add -n %s", node)
|
|
}
|
|
|
|
cmd = fmt.Sprintf("%s %s %s", cmd, bus, strings.Join(paramsKvs, ","))
|
|
m.HumanMonitorCommand(cmd, callback)
|
|
}
|
|
|
|
func (m *QmpMonitor) DeviceAdd(dev string, params map[string]string, callback StringCallback) {
|
|
args := map[string]interface{}{
|
|
"driver": dev,
|
|
}
|
|
|
|
for k, v := range params {
|
|
args[k] = v
|
|
}
|
|
|
|
cmd := &Command{
|
|
Execute: "device_add",
|
|
Args: args,
|
|
}
|
|
|
|
cb := func(res *Response) {
|
|
callback(m.actionResult(res))
|
|
}
|
|
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) MigrateSetDowntime(dtSec float64, callback StringCallback) {
|
|
m.HumanMonitorCommand(fmt.Sprintf("migrate_set_downtime %f", dtSec), callback)
|
|
}
|
|
|
|
func (m *QmpMonitor) MigrateSetCapability(capability, state string, callback StringCallback) {
|
|
var (
|
|
cb = func(res *Response) {
|
|
callback(m.actionResult(res))
|
|
}
|
|
st = false
|
|
)
|
|
if state == "on" {
|
|
st = true
|
|
}
|
|
|
|
cmd := &Command{
|
|
Execute: "migrate-set-capabilities",
|
|
Args: map[string]interface{}{
|
|
"capabilities": []interface{}{
|
|
map[string]interface{}{
|
|
"capability": capability,
|
|
"state": st,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) MigrateSetParameter(key string, val interface{}, callback StringCallback) {
|
|
var (
|
|
cb = func(res *Response) {
|
|
callback(m.actionResult(res))
|
|
}
|
|
cmd = &Command{
|
|
Execute: "migrate-set-parameters",
|
|
Args: map[string]interface{}{
|
|
key: val,
|
|
},
|
|
}
|
|
)
|
|
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) MigrateIncoming(address string, callback StringCallback) {
|
|
cmd := fmt.Sprintf("migrate_incoming %s", address)
|
|
m.HumanMonitorCommand(cmd, callback)
|
|
}
|
|
|
|
func (m *QmpMonitor) MigrateContinue(state string, callback StringCallback) {
|
|
cmd := fmt.Sprintf("migrate_continue %s", state)
|
|
m.HumanMonitorCommand(cmd, callback)
|
|
}
|
|
|
|
func (m *QmpMonitor) Migrate(
|
|
destStr string, copyIncremental, copyFull bool, callback StringCallback,
|
|
) {
|
|
var (
|
|
cb = func(res *Response) {
|
|
callback(m.actionResult(res))
|
|
}
|
|
cmd = &Command{
|
|
Execute: "migrate",
|
|
Args: map[string]interface{}{
|
|
"uri": destStr,
|
|
"blk": copyFull,
|
|
"inc": copyIncremental,
|
|
},
|
|
}
|
|
)
|
|
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) GetMigrateStatus(callback StringCallback) {
|
|
var (
|
|
cmd = &Command{Execute: "query-migrate"}
|
|
cb = func(res *Response) {
|
|
if res.ErrorVal != nil {
|
|
callback(res.ErrorVal.Error())
|
|
} else {
|
|
/*
|
|
{"expected-downtime":300,"ram":{"dirty-pages-rate":0,"dirty-sync-count":1,"duplicate":2966538,"mbps":268.5672,"normal":148629,"normal-bytes":608784384,"page-size":4096,"postcopy-requests":0,"remaining":142815232,"skipped":0,"total":12902539264,"transferred":636674057},"setup-time":65,"status":"active","total-time":20002}
|
|
{"disk":{"dirty-pages-rate":0,"dirty-sync-count":0,"duplicate":0,"mbps":0,"normal":0,"normal-bytes":0,"page-size":0,"postcopy-requests":0,"remaining":0,"skipped":0,"total":139586437120,"transferred":139586437120},"expected-downtime":300,"ram":{"dirty-pages-rate":0,"dirty-sync-count":1,"duplicate":193281,"mbps":268.44264,"normal":62311,"normal-bytes":255225856,"page-size":4096,"postcopy-requests":0,"remaining":44474368,"skipped":0,"total":1091379200,"transferred":257555032},"setup-time":15,"status":"active","total-time":10002}
|
|
*/
|
|
ret, err := jsonutils.Parse(res.Return)
|
|
if err != nil {
|
|
log.Errorf("Parse qmp res error %s: %s", m.server, err)
|
|
callback("")
|
|
} else {
|
|
log.Infof("Query migrate status %s: %s", m.server, ret.String())
|
|
|
|
status, _ := ret.GetString("status")
|
|
if status == "active" {
|
|
ramTotal, _ := ret.Int("ram", "total")
|
|
ramRemain, _ := ret.Int("ram", "remaining")
|
|
ramMbps, _ := ret.Float("ram", "mbps")
|
|
diskTotal, _ := ret.Int("disk", "total")
|
|
diskRemain, _ := ret.Int("disk", "remaining")
|
|
diskMbps, _ := ret.Float("disk", "mbps")
|
|
if diskRemain > 0 {
|
|
status = "migrate_disk_copy"
|
|
} else if ramRemain > 0 {
|
|
status = "migrate_ram_copy"
|
|
}
|
|
mbps := ramMbps + diskMbps
|
|
progress := (1 - float64(diskRemain+ramRemain)/float64(diskTotal+ramTotal)) * 100.0
|
|
log.Debugf("progress: %f mbps: %f", progress, mbps)
|
|
hostutils.UpdateServerProgress(context.Background(), m.sid, progress, mbps)
|
|
}
|
|
|
|
callback(status)
|
|
}
|
|
}
|
|
}
|
|
)
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) GetMigrateStats(callback MigrateStatsCallback) {
|
|
var (
|
|
cmd = &Command{Execute: "query-migrate"}
|
|
cb = func(res *Response) {
|
|
if res.ErrorVal != nil {
|
|
callback(nil, errors.Errorf(res.ErrorVal.Error()))
|
|
} else {
|
|
migStats := new(MigrationInfo)
|
|
err := json.Unmarshal(res.Return, migStats)
|
|
if err != nil {
|
|
callback(nil, err)
|
|
return
|
|
}
|
|
callback(migStats, nil)
|
|
}
|
|
}
|
|
)
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) MigrateCancel(cb StringCallback) {
|
|
m.HumanMonitorCommand("migrate_cancel", cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) MigrateStartPostcopy(callback StringCallback) {
|
|
var (
|
|
cmd = &Command{Execute: "migrate-start-postcopy"}
|
|
cb = func(res *Response) {
|
|
if res.ErrorVal != nil {
|
|
callback(res.ErrorVal.Error())
|
|
} else {
|
|
ret, err := jsonutils.Parse(res.Return)
|
|
if err != nil {
|
|
log.Errorf("Parse qmp res error %s: %s", m.server, err)
|
|
callback("MigrateStartPostcopy error")
|
|
} else {
|
|
log.Infof("MigrateStartPostcopy %s: %s", m.server, ret.String())
|
|
callback("")
|
|
}
|
|
}
|
|
}
|
|
)
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) blockJobs(res *Response) ([]BlockJob, error) {
|
|
if res.ErrorVal != nil {
|
|
return nil, errors.Errorf("GetBlockJobs for %s %s", m.server, jsonutils.Marshal(res.ErrorVal).String())
|
|
}
|
|
ret, err := jsonutils.Parse(res.Return)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "GetBlockJobs for %s parse %s", m.server, res.Return)
|
|
}
|
|
log.Debugf("blockJobs response %s", ret)
|
|
jobs := []BlockJob{}
|
|
if err = ret.Unmarshal(&jobs); err != nil {
|
|
return nil, err
|
|
}
|
|
return jobs, nil
|
|
}
|
|
|
|
func (m *QmpMonitor) GetBlockJobCounts(callback func(jobs int)) {
|
|
var cb = func(res *Response) {
|
|
jobs, err := m.blockJobs(res)
|
|
if err != nil {
|
|
callback(-1)
|
|
return
|
|
}
|
|
callback(len(jobs))
|
|
}
|
|
m.Query(&Command{Execute: "query-block-jobs"}, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) GetBlockJobs(callback func([]BlockJob)) {
|
|
var cb = func(res *Response) {
|
|
jobs, _ := m.blockJobs(res)
|
|
callback(jobs)
|
|
}
|
|
m.Query(&Command{Execute: "query-block-jobs"}, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) ReloadDiskBlkdev(device, path string, callback StringCallback) {
|
|
var (
|
|
cb = func(res *Response) {
|
|
callback(m.actionResult(res))
|
|
}
|
|
cmd = &Command{
|
|
Execute: "reload-disk-snapshot-blkdev-sync",
|
|
Args: map[string]string{
|
|
"device": device,
|
|
"snapshot-file": path,
|
|
"mode": "existing",
|
|
"format": "qcow2",
|
|
},
|
|
}
|
|
)
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) DriveMirror(callback StringCallback, drive, target, syncMode, format string, unmap, blockReplication bool, speed int64) {
|
|
var (
|
|
cb = func(res *Response) {
|
|
callback(m.actionResult(res))
|
|
}
|
|
args = map[string]interface{}{
|
|
"device": drive,
|
|
"target": target,
|
|
"mode": "existing",
|
|
"sync": syncMode,
|
|
"unmap": unmap,
|
|
}
|
|
)
|
|
if speed > 0 {
|
|
args["speed"] = speed
|
|
}
|
|
if blockReplication {
|
|
args["block-replication"] = true
|
|
}
|
|
cmd := &Command{
|
|
Execute: "drive-mirror",
|
|
Args: args,
|
|
}
|
|
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) DriveBackup(callback StringCallback, drive, target, syncMode, format string) {
|
|
var (
|
|
cb = func(res *Response) {
|
|
callback(m.actionResult(res))
|
|
}
|
|
args = map[string]interface{}{
|
|
"device": drive,
|
|
"target": target,
|
|
"mode": "existing",
|
|
"sync": syncMode,
|
|
"format": format,
|
|
}
|
|
)
|
|
cmd := &Command{
|
|
Execute: "drive-backup",
|
|
Args: args,
|
|
}
|
|
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) BlockStream(drive string, callback StringCallback) {
|
|
var (
|
|
speed = 5 * 100 * 1024 * 1024 // limit 500 MB/s
|
|
cb = func(res *Response) {
|
|
callback(m.actionResult(res))
|
|
}
|
|
cmd = &Command{
|
|
Execute: "block-stream",
|
|
Args: map[string]interface{}{
|
|
"device": drive,
|
|
"speed": speed,
|
|
},
|
|
}
|
|
)
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) SetVncPassword(proto, password string, callback StringCallback) {
|
|
if len(password) > 8 {
|
|
password = password[:8]
|
|
}
|
|
var (
|
|
cb = func(res *Response) {
|
|
callback(m.actionResult(res))
|
|
}
|
|
cmd = &Command{
|
|
Execute: "set_password",
|
|
Args: map[string]interface{}{
|
|
"protocol": proto,
|
|
"password": password,
|
|
},
|
|
}
|
|
)
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) StartNbdServer(port int, exportAllDevice, writable bool, callback StringCallback) {
|
|
var cmd = "nbd_server_start"
|
|
if exportAllDevice {
|
|
cmd += " -a"
|
|
}
|
|
if writable {
|
|
cmd += " -w"
|
|
}
|
|
cmd += fmt.Sprintf(" 0.0.0.0:%d", port)
|
|
m.HumanMonitorCommand(cmd, callback)
|
|
}
|
|
|
|
func (m *QmpMonitor) StopNbdServer(callback StringCallback) {
|
|
m.HumanMonitorCommand("nbd_server_stop", callback)
|
|
}
|
|
|
|
func (m *QmpMonitor) ResizeDisk(driveName string, sizeMB int64, callback StringCallback) {
|
|
cmd := fmt.Sprintf("block_resize %s %d", driveName, sizeMB)
|
|
m.HumanMonitorCommand(cmd, callback)
|
|
}
|
|
|
|
func (m *QmpMonitor) GetCpuCount(callback func(count int)) {
|
|
var cb = func(res string) {
|
|
cpus := strings.Split(res, "\\n")
|
|
count := 0
|
|
for _, cpuInfo := range cpus {
|
|
if len(strings.TrimSpace(cpuInfo)) > 0 {
|
|
count += 1
|
|
}
|
|
}
|
|
callback(count)
|
|
}
|
|
m.HumanMonitorCommand("info cpus", cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) AddCpu(cpuIndex int, callback StringCallback) {
|
|
var (
|
|
cb = func(res *Response) {
|
|
callback(m.actionResult(res))
|
|
}
|
|
cmd = &Command{
|
|
Execute: "cpu-add",
|
|
Args: map[string]interface{}{"id": cpuIndex},
|
|
}
|
|
)
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) ObjectAdd(objectType string, params map[string]string, callback StringCallback) {
|
|
var paramsKvs = []string{}
|
|
for k, v := range params {
|
|
paramsKvs = append(paramsKvs, fmt.Sprintf("%s=%s", k, v))
|
|
}
|
|
cmd := fmt.Sprintf("object_add %s,%s", objectType, strings.Join(paramsKvs, ","))
|
|
m.HumanMonitorCommand(cmd, callback)
|
|
}
|
|
|
|
func (m *QmpMonitor) GeMemtSlotIndex(callback func(index int)) {
|
|
var cb = func(res string) {
|
|
memInfos := strings.Split(res, "\\n")
|
|
var count int
|
|
for _, line := range memInfos {
|
|
if strings.HasPrefix(strings.TrimSpace(line), "slot:") {
|
|
count += 1
|
|
}
|
|
}
|
|
callback(count)
|
|
}
|
|
m.HumanMonitorCommand("info memory-devices", cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) GetMemoryDevicesInfo(callback QueryMemoryDevicesCallback) {
|
|
var (
|
|
cb = func(res *Response) {
|
|
if res.ErrorVal != nil {
|
|
callback(nil, res.ErrorVal.Error())
|
|
} else {
|
|
memDevices := make([]MemoryDeviceInfo, 0)
|
|
err := json.Unmarshal(res.Return, &memDevices)
|
|
if err != nil {
|
|
callback(nil, err.Error())
|
|
} else {
|
|
callback(memDevices, "")
|
|
}
|
|
}
|
|
}
|
|
cmd = &Command{
|
|
Execute: "query-memory-devices",
|
|
}
|
|
)
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) GetMemdevList(callback MemdevListCallback) {
|
|
var (
|
|
cb = func(res *Response) {
|
|
if res.ErrorVal != nil {
|
|
callback(nil, res.ErrorVal.Error())
|
|
} else {
|
|
memdevList := make([]Memdev, 0)
|
|
err := json.Unmarshal(res.Return, &memdevList)
|
|
if err != nil {
|
|
callback(nil, err.Error())
|
|
} else {
|
|
callback(memdevList, "")
|
|
}
|
|
|
|
}
|
|
}
|
|
cmd = &Command{
|
|
Execute: "query-memdev",
|
|
}
|
|
)
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) BlockIoThrottle(driveName string, bps, iops int64, callback StringCallback) {
|
|
cmd := fmt.Sprintf("block_set_io_throttle %s %d 0 0 %d 0 0", driveName, bps, iops)
|
|
m.HumanMonitorCommand(cmd, callback)
|
|
}
|
|
|
|
func (m *QmpMonitor) CancelBlockJob(driveName string, force bool, callback StringCallback) {
|
|
cmd := "block_job_cancel "
|
|
if force {
|
|
cmd += "-f "
|
|
}
|
|
cmd += driveName
|
|
m.HumanMonitorCommand(cmd, callback)
|
|
}
|
|
|
|
func (m *QmpMonitor) BlockJobComplete(drive string, callback StringCallback) {
|
|
m.HumanMonitorCommand(fmt.Sprintf("block_job_complete %s", drive), callback)
|
|
}
|
|
|
|
func (m *QmpMonitor) BlockReopenImage(drive, newImagePath, format string, callback StringCallback) {
|
|
var cb = func(res *Response) {
|
|
callback(m.actionResult(res))
|
|
}
|
|
|
|
var cmd = &Command{
|
|
Execute: "block_reopen_image",
|
|
Args: map[string]interface{}{
|
|
"device": drive,
|
|
"new_image": newImagePath,
|
|
"format": format,
|
|
},
|
|
}
|
|
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) SnapshotBlkdev(drive, newImagePath, format string, reuse bool, callback StringCallback) {
|
|
var cmd = "snapshot_blkdev"
|
|
if reuse {
|
|
cmd += " -n"
|
|
}
|
|
cmd += fmt.Sprintf(" %s %s %s", drive, newImagePath, format)
|
|
|
|
m.HumanMonitorCommand(cmd, callback)
|
|
}
|
|
|
|
func (m *QmpMonitor) NetdevAdd(id, netType string, params map[string]string, callback StringCallback) {
|
|
cmd := fmt.Sprintf("netdev_add %s,id=%s", netType, id)
|
|
for k, v := range params {
|
|
cmd += fmt.Sprintf(",%s=%s", k, v)
|
|
}
|
|
m.HumanMonitorCommand(cmd, callback)
|
|
}
|
|
|
|
func (m *QmpMonitor) NetdevDel(id string, callback StringCallback) {
|
|
cmd := fmt.Sprintf("netdev_del %s", id)
|
|
m.HumanMonitorCommand(cmd, callback)
|
|
}
|
|
|
|
func (m *QmpMonitor) SaveState(stateFilePath string, callback StringCallback) {
|
|
var (
|
|
cb = func(res *Response) {
|
|
callback(m.actionResult(res))
|
|
}
|
|
cmd = &Command{
|
|
Execute: "migrate",
|
|
Args: map[string]interface{}{
|
|
"uri": getSaveStatefileUri(stateFilePath),
|
|
},
|
|
}
|
|
)
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) QueryPci(callback QueryPciCallback) {
|
|
var (
|
|
cb = func(res *Response) {
|
|
if res.ErrorVal != nil {
|
|
callback(nil, res.ErrorVal.Error())
|
|
} else {
|
|
pciInfoList := make([]PCIInfo, 0)
|
|
err := json.Unmarshal(res.Return, &pciInfoList)
|
|
if err != nil {
|
|
callback(nil, err.Error())
|
|
} else {
|
|
callback(pciInfoList, "")
|
|
}
|
|
}
|
|
}
|
|
cmd = &Command{
|
|
Execute: "query-pci",
|
|
}
|
|
)
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) QueryMachines(callback QueryMachinesCallback) {
|
|
var (
|
|
cb = func(res *Response) {
|
|
if res.ErrorVal != nil {
|
|
callback(nil, res.ErrorVal.Error())
|
|
} else {
|
|
machineInfoList := make([]MachineInfo, 0)
|
|
err := json.Unmarshal(res.Return, &machineInfoList)
|
|
if err != nil {
|
|
callback(nil, err.Error())
|
|
} else {
|
|
callback(machineInfoList, "")
|
|
}
|
|
}
|
|
}
|
|
cmd = &Command{
|
|
Execute: "query-machines",
|
|
}
|
|
)
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) Quit(callback StringCallback) {
|
|
var (
|
|
cb = func(res *Response) {
|
|
if res.ErrorVal != nil {
|
|
callback(res.ErrorVal.Error())
|
|
} else {
|
|
callback(string(res.Return))
|
|
}
|
|
}
|
|
cmd = &Command{
|
|
Execute: "quit",
|
|
}
|
|
)
|
|
m.Query(cmd, cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) InfoQtree(cb StringCallback) {
|
|
m.HumanMonitorCommand("info qtree", cb)
|
|
}
|
|
|
|
func (m *QmpMonitor) GetHotPluggableCpus(callback HotpluggableCPUListCallback) {
|
|
var (
|
|
cb = func(res *Response) {
|
|
if res.ErrorVal != nil {
|
|
callback(nil, res.ErrorVal.Error())
|
|
} else {
|
|
cpuList := make([]HotpluggableCPU, 0)
|
|
err := json.Unmarshal(res.Return, &cpuList)
|
|
if err != nil {
|
|
callback(nil, err.Error())
|
|
} else {
|
|
callback(cpuList, "")
|
|
}
|
|
}
|
|
}
|
|
cmd = &Command{
|
|
Execute: "query-hotpluggable-cpus",
|
|
}
|
|
)
|
|
m.Query(cmd, cb)
|
|
}
|