mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/yunionio/cloudpods.git
synced 2026-09-20 08:03:53 +08:00
feat(region,host): qga set user password
Signed-off-by: wanyaoqi <d3lx.yq@gmail.com>
This commit is contained in:
@@ -105,6 +105,7 @@ func init() {
|
||||
cmd.Perform("calculate-record-checksum", &options.ServerIdOptions{})
|
||||
cmd.Perform("set-class-metadata", &baseoptions.ResourceMetadataOptions{})
|
||||
cmd.BatchPerform("enable-memclean", new(options.ServerIdsOptions))
|
||||
cmd.Perform("qga-set-password", &options.ServerQgaSetPassword{})
|
||||
|
||||
cmd.Get("vnc", new(options.ServerIdOptions))
|
||||
cmd.Get("desc", new(options.ServerIdOptions))
|
||||
|
||||
@@ -154,6 +154,8 @@ const (
|
||||
|
||||
VM_SYNC_ISOLATED_DEVICE_FAILED = "sync_isolated_device_failed"
|
||||
|
||||
VM_RESET_PASSWORD = "reset_password"
|
||||
|
||||
SHUTDOWN_STOP = "stop"
|
||||
SHUTDOWN_TERMINATE = "terminate"
|
||||
|
||||
@@ -186,6 +188,13 @@ const (
|
||||
HYPERVISOR_DEFAULT = HYPERVISOR_KVM
|
||||
)
|
||||
|
||||
const (
|
||||
QGA_STATUS_UNKNOWN = "unknown"
|
||||
QGA_STATUS_EXCUTING = "executing"
|
||||
QGA_STATUS_EXECUTE_FAILED = "execute_failed"
|
||||
QGA_STATUS_AVAILABLE = "available"
|
||||
)
|
||||
|
||||
const (
|
||||
CPU_MODE_QEMU = "qemu"
|
||||
CPU_MODE_HOST = "host"
|
||||
|
||||
@@ -889,3 +889,8 @@ type ServerQemuInfo struct {
|
||||
Version string `json:"version"`
|
||||
Cmdline string `json:"cmdline"`
|
||||
}
|
||||
|
||||
type ServerQgaSetPasswordInput struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
@@ -31,3 +31,9 @@ type HostTopology struct {
|
||||
type HostCPUInfo struct {
|
||||
*cpu.Info
|
||||
}
|
||||
|
||||
type GuestSetPasswordRequest struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Crypted bool `json:"crypted"`
|
||||
}
|
||||
|
||||
@@ -150,6 +150,9 @@ const (
|
||||
ACT_VM_DEPLOY = "deploy"
|
||||
ACT_VM_DEPLOY_FAIL = "deploy_fail"
|
||||
|
||||
ACT_SET_USER_PASSWORD = "set_user_password"
|
||||
ACT_SET_USER_PASSWORD_FAIL = "set_user_password_fail"
|
||||
|
||||
ACT_VM_IO_THROTTLE = "io_throttle"
|
||||
ACT_VM_IO_THROTTLE_FAIL = "io_throttle_fail"
|
||||
|
||||
|
||||
@@ -465,3 +465,11 @@ func (self *SBaseGuestDriver) RequestCPUSet(ctx context.Context, userCred mcclie
|
||||
func (self *SBaseGuestDriver) RequestCPUSetRemove(ctx context.Context, userCred mcclient.TokenCredential, host *models.SHost, guest *models.SGuest, input *api.ServerCPUSetRemoveInput) error {
|
||||
return httperrors.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (self *SBaseGuestDriver) QgaRequestGuestPing(ctx context.Context, task taskman.ITask, host *models.SHost, guest *models.SGuest) error {
|
||||
return httperrors.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (self *SBaseGuestDriver) QgaRequestSetUserPassword(ctx context.Context, task taskman.ITask, host *models.SHost, guest *models.SGuest, input *api.ServerQgaSetPasswordInput) error {
|
||||
return httperrors.ErrNotImplemented
|
||||
}
|
||||
|
||||
@@ -985,3 +985,26 @@ func (self *SKVMGuestDriver) RequestCPUSetRemove(ctx context.Context, userCred m
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SKVMGuestDriver) QgaRequestGuestPing(ctx context.Context, task taskman.ITask, host *models.SHost, guest *models.SGuest) error {
|
||||
url := fmt.Sprintf("%s/servers/%s/qga-guest-ping", host.ManagerUri, guest.Id)
|
||||
httpClient := httputils.GetDefaultClient()
|
||||
header := task.GetTaskRequestHeader()
|
||||
_, _, err := httputils.JSONRequest(httpClient, ctx, "POST", url, header, nil, false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "host request")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SKVMGuestDriver) QgaRequestSetUserPassword(ctx context.Context, task taskman.ITask, host *models.SHost, guest *models.SGuest, input *api.ServerQgaSetPasswordInput) error {
|
||||
url := fmt.Sprintf("%s/servers/%s/qga-set-password", host.ManagerUri, guest.Id)
|
||||
httpClient := httputils.GetDefaultClient()
|
||||
header := task.GetTaskRequestHeader()
|
||||
body := jsonutils.Marshal(input)
|
||||
_, _, err := httputils.JSONRequest(httpClient, ctx, "POST", url, header, body, false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "host request")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -221,6 +221,9 @@ type IGuestDriver interface {
|
||||
|
||||
RequestCPUSet(ctx context.Context, userCred mcclient.TokenCredential, host *SHost, guest *SGuest, input *api.ServerCPUSetInput) (*api.ServerCPUSetResp, error)
|
||||
RequestCPUSetRemove(ctx context.Context, userCred mcclient.TokenCredential, host *SHost, guest *SGuest, input *api.ServerCPUSetRemoveInput) error
|
||||
|
||||
QgaRequestGuestPing(ctx context.Context, task taskman.ITask, host *SHost, guest *SGuest) error
|
||||
QgaRequestSetUserPassword(ctx context.Context, task taskman.ITask, host *SHost, guest *SGuest, input *api.ServerQgaSetPasswordInput) error
|
||||
}
|
||||
|
||||
var guestDrivers map[string]IGuestDriver
|
||||
|
||||
@@ -170,6 +170,8 @@ type SGuest struct {
|
||||
InternetMaxBandwidthOut int `nullable:"true" list:"user" create:"optional"`
|
||||
// 磁盘吞吐量
|
||||
Throughput int `nullable:"true" list:"user" create:"optional"`
|
||||
|
||||
QgaStatus string `width:"36" charset:"ascii" nullable:"false" default:"unknown" list:"user" create:"optional"`
|
||||
}
|
||||
|
||||
func (manager *SGuestManager) GetPropertyStatistics(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*apis.StatusStatistic, error) {
|
||||
|
||||
56
pkg/compute/models/qemu_guest_agent.go
Normal file
56
pkg/compute/models/qemu_guest_agent.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/util/seclib2"
|
||||
)
|
||||
|
||||
func (self *SGuest) UpdateQgaStatus(status string) error {
|
||||
_, err := db.Update(self, func() error {
|
||||
self.QgaStatus = status
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Update QgaStatus")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SGuest) PerformQgaSetPassword(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject,
|
||||
input *api.ServerQgaSetPasswordInput,
|
||||
) (jsonutils.JSONObject, error) {
|
||||
if self.Status != api.VM_RUNNING {
|
||||
return nil, httperrors.NewBadRequestError("can't use qga in vm status: %s", self.Status)
|
||||
}
|
||||
if input.Username == "" {
|
||||
return nil, httperrors.NewMissingParameterError("username")
|
||||
}
|
||||
if input.Password == "" {
|
||||
return nil, httperrors.NewMissingParameterError("password")
|
||||
}
|
||||
err := seclib2.ValidatePassword(input.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
self.SetStatus(userCred, api.VM_RESET_PASSWORD, "")
|
||||
self.UpdateQgaStatus(api.QGA_STATUS_EXCUTING)
|
||||
params := jsonutils.Marshal(input).(*jsonutils.JSONDict)
|
||||
task, err := taskman.TaskManager.NewTask(ctx, "GuestQgaSetPasswordTask", self, userCred, params, "", "", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
task.ScheduleRun(nil)
|
||||
return nil, nil
|
||||
}
|
||||
89
pkg/compute/tasks/guest_qga_reset_password_task.go
Normal file
89
pkg/compute/tasks/guest_qga_reset_password_task.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/utils"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/compute/models"
|
||||
"yunion.io/x/onecloud/pkg/util/logclient"
|
||||
)
|
||||
|
||||
type SGuestQgaBaseTask struct {
|
||||
SGuestBaseTask
|
||||
}
|
||||
|
||||
func (self *SGuestQgaBaseTask) guestPing(ctx context.Context, guest *models.SGuest) error {
|
||||
host, err := guest.GetHost()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return guest.GetDriver().QgaRequestGuestPing(ctx, self, host, guest)
|
||||
}
|
||||
|
||||
func (self *SGuestQgaBaseTask) taskFailed(ctx context.Context, guest *models.SGuest, reason string) {
|
||||
guest.SetStatus(self.UserCred, api.VM_RUNNING, "on qga set user password failed")
|
||||
guest.UpdateQgaStatus(api.QGA_STATUS_EXECUTE_FAILED)
|
||||
db.OpsLog.LogEvent(guest, db.ACT_SET_USER_PASSWORD_FAIL, reason, self.UserCred)
|
||||
logclient.AddActionLogWithContext(ctx, guest, logclient.ACT_SET_USER_PASSWORD, reason, self.UserCred, false)
|
||||
self.SetStageFailed(ctx, jsonutils.NewString(reason))
|
||||
}
|
||||
|
||||
type GuestQgaSetPasswordTask struct {
|
||||
SGuestQgaBaseTask
|
||||
}
|
||||
|
||||
func init() {
|
||||
taskman.RegisterTask(GuestQgaSetPasswordTask{})
|
||||
}
|
||||
|
||||
func (self *GuestQgaSetPasswordTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
|
||||
guest := obj.(*models.SGuest)
|
||||
self.SetStage("OnQgaGuestPing", nil)
|
||||
if err := self.guestPing(ctx, guest); err != nil {
|
||||
self.OnQgaGuestPingFailed(ctx, guest, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *GuestQgaSetPasswordTask) OnQgaGuestPing(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
|
||||
input := &api.ServerQgaSetPasswordInput{}
|
||||
self.GetParams().Unmarshal(input)
|
||||
self.SetStage("OnQgaSetUserPassword", nil)
|
||||
host, err := guest.GetHost()
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, guest, err.Error())
|
||||
return
|
||||
}
|
||||
err = guest.GetDriver().QgaRequestSetUserPassword(ctx, self, host, guest, input)
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, guest, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (self *GuestQgaSetPasswordTask) OnQgaGuestPingFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
|
||||
self.taskFailed(ctx, guest, data.String())
|
||||
}
|
||||
|
||||
func (self *GuestQgaSetPasswordTask) OnQgaSetUserPassword(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
|
||||
guest.SetStatus(self.UserCred, api.VM_RUNNING, "on qga set user password success")
|
||||
db.OpsLog.LogEvent(guest, db.ACT_SET_USER_PASSWORD, "", self.UserCred)
|
||||
|
||||
input := &api.ServerQgaSetPasswordInput{}
|
||||
self.GetParams().Unmarshal(input)
|
||||
info := make(map[string]interface{})
|
||||
secret, _ := utils.EncryptAESBase64(guest.Id, input.Password)
|
||||
info["login_account"] = input.Username
|
||||
info["login_key"] = secret
|
||||
guest.SetAllMetadata(ctx, info, self.UserCred)
|
||||
|
||||
logclient.AddActionLogWithContext(ctx, guest, logclient.ACT_SET_USER_PASSWORD, "", self.UserCred, true)
|
||||
self.SetStageComplete(ctx, nil)
|
||||
}
|
||||
|
||||
func (self *GuestQgaSetPasswordTask) OnQgaSetUserPasswordFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
|
||||
self.taskFailed(ctx, guest, data.String())
|
||||
}
|
||||
86
pkg/hostman/guestman/guest-agent.go
Normal file
86
pkg/hostman/guestman/guest-agent.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package guestman
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
)
|
||||
|
||||
const (
|
||||
QGA_LOCK_TIMEOUT = time.Second * 10
|
||||
QGA_EXEC_TIMEOUT = time.Second * 5
|
||||
)
|
||||
|
||||
func qgaExec(timeout time.Duration, qgaFunc func(chan error)) error {
|
||||
c := make(chan error, 1)
|
||||
go qgaFunc(c)
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
return errors.Errorf("qga command no resp after %fs", timeout.Seconds())
|
||||
case err := <-c:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SGuestManager) checkAndInitGuestQga(sid string) (*SKVMGuestInstance, error) {
|
||||
guest, _ := m.GetServer(sid)
|
||||
if guest == nil {
|
||||
return nil, httperrors.NewNotFoundError("Not found guest by id %s", sid)
|
||||
}
|
||||
if !guest.IsRunning() {
|
||||
return nil, httperrors.NewBadRequestError("Guest %s is not in state running", sid)
|
||||
}
|
||||
if guest.guestAgent == nil {
|
||||
if err := guest.InitQga(); err != nil {
|
||||
return nil, errors.Wrap(err, "init qga")
|
||||
}
|
||||
}
|
||||
return guest, nil
|
||||
}
|
||||
|
||||
func (m *SGuestManager) QgaGuestSetPassword(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
|
||||
input := params.(*SQgaGuestSetPassword)
|
||||
guest, err := m.checkAndInitGuestQga(input.Sid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if guest.guestAgent.TryLock(QGA_LOCK_TIMEOUT) {
|
||||
defer guest.guestAgent.Unlock()
|
||||
} else {
|
||||
return nil, errors.Wrap(err, "qga unfinished last cmd, is qga unavailable?")
|
||||
}
|
||||
f := func(c chan error) {
|
||||
if guest.guestAgent.TryLock(QGA_LOCK_TIMEOUT) {
|
||||
defer guest.guestAgent.Unlock()
|
||||
c <- guest.guestAgent.GuestSetUserPassword(input.Username, input.Password, input.Crypted)
|
||||
} else {
|
||||
c <- errors.Errorf("qga unfinished last cmd, is qga unavailable?")
|
||||
}
|
||||
}
|
||||
err = qgaExec(QGA_EXEC_TIMEOUT, f)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (m *SGuestManager) QgaGuestPing(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
|
||||
input := params.(*SBaseParms)
|
||||
guest, err := m.checkAndInitGuestQga(input.Sid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f := func(c chan error) {
|
||||
if guest.guestAgent.TryLock(QGA_LOCK_TIMEOUT) {
|
||||
defer guest.guestAgent.Unlock()
|
||||
c <- guest.guestAgent.GuestPing()
|
||||
} else {
|
||||
c <- errors.Errorf("qga unfinished last cmd, is qga unavailable?")
|
||||
}
|
||||
}
|
||||
err = qgaExec(QGA_EXEC_TIMEOUT, f)
|
||||
return nil, err
|
||||
}
|
||||
@@ -91,6 +91,8 @@ func AddGuestTaskHandler(prefix string, app *appsrv.Application) {
|
||||
"cpuset-remove": guestCPUSetRemove,
|
||||
"memory-snapshot": guestMemorySnapshot,
|
||||
"memory-snapshot-reset": guestMemorySnapshotReset,
|
||||
"qga-set-password": qgaGuestSetPassword,
|
||||
"qga-guest-ping": qgaGuestPing,
|
||||
} {
|
||||
app.AddHandler("POST",
|
||||
fmt.Sprintf("%s/%s/<sid>/%s", prefix, keyWord, action),
|
||||
@@ -754,3 +756,28 @@ func guestMemorySnapshotDelete(ctx context.Context, w http.ResponseWriter, r *ht
|
||||
GuestMemorySnapshotDeleteRequest: input,
|
||||
})
|
||||
}
|
||||
|
||||
func qgaGuestSetPassword(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
|
||||
input := new(hostapi.GuestSetPasswordRequest)
|
||||
if err := body.Unmarshal(input); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if input.Username == "" {
|
||||
return nil, httperrors.NewMissingParameterError("username")
|
||||
}
|
||||
if input.Password == "" {
|
||||
return nil, httperrors.NewMissingParameterError("password")
|
||||
}
|
||||
gm := guestman.GetGuestManager()
|
||||
hostutils.DelayTask(ctx, gm.QgaGuestSetPassword, &guestman.SQgaGuestSetPassword{
|
||||
GuestSetPasswordRequest: input,
|
||||
Sid: sid,
|
||||
})
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func qgaGuestPing(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
|
||||
gm := guestman.GetGuestManager()
|
||||
hostutils.DelayTask(ctx, gm.QgaGuestPing, &guestman.SBaseParms{Sid: sid})
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -161,3 +161,8 @@ type SEsxiAccessInfo struct {
|
||||
HostIp string
|
||||
GuestExtId string
|
||||
}
|
||||
|
||||
type SQgaGuestSetPassword struct {
|
||||
*hostapi.GuestSetPasswordRequest
|
||||
Sid string
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/hostman/hostinfo/hostconsts"
|
||||
"yunion.io/x/onecloud/pkg/hostman/hostutils"
|
||||
"yunion.io/x/onecloud/pkg/hostman/monitor"
|
||||
"yunion.io/x/onecloud/pkg/hostman/monitor/qga"
|
||||
"yunion.io/x/onecloud/pkg/hostman/options"
|
||||
"yunion.io/x/onecloud/pkg/hostman/storageman"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
@@ -86,6 +87,7 @@ type SKVMGuestInstance struct {
|
||||
Desc *jsonutils.JSONDict
|
||||
Monitor monitor.Monitor
|
||||
manager *SGuestManager
|
||||
guestAgent *qga.QemuGuestAgent
|
||||
startupTask *SGuestResumeTask
|
||||
migrateTask *SGuestLiveMigrateTask
|
||||
stopping bool
|
||||
@@ -768,6 +770,19 @@ func (s *SKVMGuestInstance) eventBlockJobReady(event *monitor.Event) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SKVMGuestInstance) QgaPath() string {
|
||||
return path.Join(s.HomeDir(), "qga.sock")
|
||||
}
|
||||
|
||||
func (s *SKVMGuestInstance) InitQga() error {
|
||||
guestAgent, err := qga.NewQemuGuestAgent(s.Id, s.QgaPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.guestAgent = guestAgent
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SKVMGuestInstance) SyncMirrorJobFailed(reason string) {
|
||||
params := jsonutils.NewDict()
|
||||
params.Set("reason", jsonutils.NewString(reason))
|
||||
@@ -830,18 +845,23 @@ func (s *SKVMGuestInstance) onGetQemuVersion(ctx context.Context, version string
|
||||
}
|
||||
} else if s.IsSlave() {
|
||||
s.startQemuBuiltInNbdServer(ctx)
|
||||
} else if s.IsMaster() {
|
||||
s.startDiskBackupMirror(ctx)
|
||||
if ctx != nil && len(appctx.AppContextTaskId(ctx)) > 0 {
|
||||
s.DoResumeTask(ctx, false)
|
||||
} else {
|
||||
if options.HostOptions.SetVncPassword {
|
||||
s.SetVncPassword()
|
||||
}
|
||||
s.OnResumeSyncMetadataInfo()
|
||||
}
|
||||
} else {
|
||||
s.DoResumeTask(ctx, true)
|
||||
if s.IsMaster() {
|
||||
s.startDiskBackupMirror(ctx)
|
||||
if ctx != nil && len(appctx.AppContextTaskId(ctx)) > 0 {
|
||||
s.DoResumeTask(ctx, false)
|
||||
} else {
|
||||
if options.HostOptions.SetVncPassword {
|
||||
s.SetVncPassword()
|
||||
}
|
||||
s.OnResumeSyncMetadataInfo()
|
||||
}
|
||||
} else {
|
||||
s.DoResumeTask(ctx, true)
|
||||
}
|
||||
if err := s.InitQga(); err != nil {
|
||||
log.Errorf("Guest %s init qga failed %s", s.Id, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -852,6 +872,10 @@ func (s *SKVMGuestInstance) onMonitorDisConnect(err error) {
|
||||
if !jsonutils.QueryBoolean(s.Desc, "is_slave", false) {
|
||||
s.SyncStatus(fmt.Sprintf("monitor disconnect %v", err))
|
||||
}
|
||||
if s.guestAgent != nil {
|
||||
s.guestAgent.Close()
|
||||
s.guestAgent = nil
|
||||
}
|
||||
s.clearCgroup(0)
|
||||
s.Monitor = nil
|
||||
}
|
||||
|
||||
15
pkg/hostman/monitor/qga/doc.go
Normal file
15
pkg/hostman/monitor/qga/doc.go
Normal file
@@ -0,0 +1,15 @@
|
||||
// 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 qga // import "yunion.io/x/onecloud/pkg/hostman/monitor/qga"
|
||||
331
pkg/hostman/monitor/qga/qga.go
Normal file
331
pkg/hostman/monitor/qga/qga.go
Normal file
@@ -0,0 +1,331 @@
|
||||
package qga
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/hostman/monitor"
|
||||
)
|
||||
|
||||
type QemuGuestAgent struct {
|
||||
id string
|
||||
|
||||
scanner *bufio.Scanner
|
||||
rwc net.Conn
|
||||
c chan struct{}
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
func NewQemuGuestAgent(id, qgaSocketPath string) (*QemuGuestAgent, error) {
|
||||
conn, err := net.Dial("unix", qgaSocketPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "dial qga socket")
|
||||
}
|
||||
return &QemuGuestAgent{
|
||||
id: id,
|
||||
rwc: conn,
|
||||
scanner: bufio.NewScanner(conn),
|
||||
c: make(chan struct{}, 1),
|
||||
mutex: &sync.Mutex{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (qga *QemuGuestAgent) Close() error {
|
||||
return qga.rwc.Close()
|
||||
}
|
||||
|
||||
func (qga *QemuGuestAgent) write(cmd []byte) error {
|
||||
log.Infof("QGA Write %s: %s", qga.id, string(cmd))
|
||||
length, index := len(cmd), 0
|
||||
for index < length {
|
||||
i, err := qga.rwc.Write(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
index += i
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lock before execute qemu guest agent commands
|
||||
func (qga *QemuGuestAgent) Lock() {
|
||||
qga.c <- struct{}{}
|
||||
}
|
||||
|
||||
// Lock before execute qemu guest agent commands
|
||||
func (qga *QemuGuestAgent) TryLock(timeout time.Duration) bool {
|
||||
select {
|
||||
case qga.c <- struct{}{}:
|
||||
return true
|
||||
case <-time.After(timeout):
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock after execute qemu guest agent commands
|
||||
func (qga *QemuGuestAgent) Unlock() {
|
||||
<-qga.c
|
||||
}
|
||||
|
||||
func (qga *QemuGuestAgent) execCmd(cmd *monitor.Command, expectResp bool) (*json.RawMessage, error) {
|
||||
rawCmd, err := json.Marshal(cmd)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "marshal qga cmd")
|
||||
}
|
||||
|
||||
err = qga.write(rawCmd)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "write cmd")
|
||||
}
|
||||
|
||||
if !expectResp {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !qga.scanner.Scan() {
|
||||
return nil, errors.Wrap(qga.scanner.Err(), "qga scanner")
|
||||
}
|
||||
var objmap map[string]*json.RawMessage
|
||||
b := qga.scanner.Bytes()
|
||||
if err := json.Unmarshal(b, &objmap); err != nil {
|
||||
return nil, errors.Wrap(err, "unmarshal qga res")
|
||||
}
|
||||
if val, ok := objmap["return"]; ok {
|
||||
return val, nil
|
||||
} else if val, ok := objmap["error"]; ok {
|
||||
res := &monitor.Error{}
|
||||
if err := json.Unmarshal(*val, res); err != nil {
|
||||
return nil, errors.Wrapf(err, "unmarshal qemu error resp: %s", *val)
|
||||
}
|
||||
return nil, errors.Errorf(res.Error())
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (qga *QemuGuestAgent) GuestPing() error {
|
||||
cmd := &monitor.Command{
|
||||
Execute: "guest-ping",
|
||||
}
|
||||
_, err := qga.execCmd(cmd, true)
|
||||
return err
|
||||
}
|
||||
|
||||
type GuestCommand struct {
|
||||
Enabled bool
|
||||
Name string
|
||||
|
||||
// whether command returns a response on success (since 1.7)
|
||||
SuccessResp bool `json:"success-response"`
|
||||
}
|
||||
|
||||
type GuestInfo struct {
|
||||
Version string
|
||||
SupportCommands []GuestCommand
|
||||
}
|
||||
|
||||
func (qga *QemuGuestAgent) GuestInfo() (*GuestInfo, error) {
|
||||
cmd := &monitor.Command{
|
||||
Execute: "guest-info",
|
||||
}
|
||||
|
||||
rawRes, err := qga.execCmd(cmd, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rawRes == nil {
|
||||
return nil, errors.Errorf("qga no response")
|
||||
}
|
||||
res := new(GuestInfo)
|
||||
err = json.Unmarshal(*rawRes, res)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unmarshal raw response")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
/*
|
||||
# @username: the user account whose password to change
|
||||
# @password: the new password entry string, base64 encoded
|
||||
# @crypted: true if password is already crypt()d, false if raw
|
||||
#
|
||||
# If the @crypted flag is true, it is the caller's responsibility
|
||||
# to ensure the correct crypt() encryption scheme is used. This
|
||||
# command does not attempt to interpret or report on the encryption
|
||||
# scheme. Refer to the documentation of the guest operating system
|
||||
# in question to determine what is supported.
|
||||
#
|
||||
# Not all guest operating systems will support use of the
|
||||
# @crypted flag, as they may require the clear-text password
|
||||
#
|
||||
# The @password parameter must always be base64 encoded before
|
||||
# transmission, even if already crypt()d, to ensure it is 8-bit
|
||||
# safe when passed as JSON.
|
||||
#
|
||||
# Returns: Nothing on success.
|
||||
#
|
||||
# Since: 2.3
|
||||
*/
|
||||
func (qga *QemuGuestAgent) GuestSetUserPassword(username, password string, crypted bool) error {
|
||||
password64 := base64.StdEncoding.EncodeToString([]byte(password))
|
||||
cmd := &monitor.Command{
|
||||
Execute: "guest-set-user-password",
|
||||
Args: map[string]interface{}{
|
||||
"username": username,
|
||||
"password": password64,
|
||||
"crypted": crypted,
|
||||
},
|
||||
}
|
||||
_, err := qga.execCmd(cmd, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
##
|
||||
# @GuestExec:
|
||||
# @pid: pid of child process in guest OS
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'struct': 'GuestExec',
|
||||
'data': { 'pid': 'int'} }
|
||||
*/
|
||||
|
||||
type GuestExec struct {
|
||||
Pid int
|
||||
}
|
||||
|
||||
/*
|
||||
##
|
||||
# @guest-exec:
|
||||
#
|
||||
# Execute a command in the guest
|
||||
#
|
||||
# @path: path or executable name to execute
|
||||
# @arg: argument list to pass to executable
|
||||
# @env: environment variables to pass to executable
|
||||
# @input-data: data to be passed to process stdin (base64 encoded)
|
||||
# @capture-output: bool flag to enable capture of
|
||||
# stdout/stderr of running process. defaults to false.
|
||||
#
|
||||
# Returns: PID on success.
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'command': 'guest-exec',
|
||||
'data': { 'path': 'str', '*arg': ['str'], '*env': ['str'],
|
||||
'*input-data': 'str', '*capture-output': 'bool' },
|
||||
'returns': 'GuestExec' }
|
||||
*/
|
||||
|
||||
func (qga *QemuGuestAgent) GuestExecCommand(
|
||||
cmdPath string, args, env []string, inputData string, captureOutput bool,
|
||||
) (*GuestExec, error) {
|
||||
qgaCmd := &monitor.Command{
|
||||
Execute: "guest-exec",
|
||||
Args: map[string]interface{}{
|
||||
"path": cmdPath,
|
||||
"arg": args,
|
||||
"env": env,
|
||||
"input-data": inputData,
|
||||
"capture-output": captureOutput,
|
||||
},
|
||||
}
|
||||
rawRes, err := qga.execCmd(qgaCmd, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rawRes == nil {
|
||||
return nil, errors.Errorf("qga no response")
|
||||
}
|
||||
res := new(GuestExec)
|
||||
err = json.Unmarshal(*rawRes, res)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unmarshal raw response")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
/*
|
||||
##
|
||||
# @GuestExecStatus:
|
||||
#
|
||||
# @exited: true if process has already terminated.
|
||||
# @exitcode: process exit code if it was normally terminated.
|
||||
# @signal: signal number (linux) or unhandled exception code
|
||||
# (windows) if the process was abnormally terminated.
|
||||
# @out-data: base64-encoded stdout of the process
|
||||
# @err-data: base64-encoded stderr of the process
|
||||
# Note: @out-data and @err-data are present only
|
||||
# if 'capture-output' was specified for 'guest-exec'
|
||||
# @out-truncated: true if stdout was not fully captured
|
||||
# due to size limitation.
|
||||
# @err-truncated: true if stderr was not fully captured
|
||||
# due to size limitation.
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'struct': 'GuestExecStatus',
|
||||
'data': { 'exited': 'bool', '*exitcode': 'int', '*signal': 'int',
|
||||
'*out-data': 'str', '*err-data': 'str',
|
||||
'*out-truncated': 'bool', '*err-truncated': 'bool' }}
|
||||
*/
|
||||
type GuestExecStatus struct {
|
||||
Exited bool
|
||||
Exitcode int
|
||||
Signal int
|
||||
OutData string `json:"out-data"`
|
||||
ErrData string `json:"err-data"`
|
||||
OutTruncated bool `json:"out-truncated"`
|
||||
ErrTruncated bool `json:"err-truncated"`
|
||||
}
|
||||
|
||||
/*
|
||||
##
|
||||
# @guest-exec-status:
|
||||
#
|
||||
# Check status of process associated with PID retrieved via guest-exec.
|
||||
# Reap the process and associated metadata if it has exited.
|
||||
#
|
||||
# @pid: pid returned from guest-exec
|
||||
#
|
||||
# Returns: GuestExecStatus on success.
|
||||
#
|
||||
# Since: 2.5
|
||||
##
|
||||
{ 'command': 'guest-exec-status',
|
||||
'data': { 'pid': 'int' },
|
||||
'returns': 'GuestExecStatus' }
|
||||
*/
|
||||
|
||||
func (qga *QemuGuestAgent) GuestExecStatusCommand(pid int) (*GuestExecStatus, error) {
|
||||
cmd := &monitor.Command{
|
||||
Execute: "guest-exec-status",
|
||||
Args: map[string]interface{}{
|
||||
"pid": pid,
|
||||
},
|
||||
}
|
||||
rawRes, err := qga.execCmd(cmd, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rawRes == nil {
|
||||
return nil, errors.Errorf("qga no response")
|
||||
}
|
||||
res := new(GuestExecStatus)
|
||||
err = json.Unmarshal(*rawRes, res)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unmarshal raw response")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
@@ -788,6 +788,17 @@ type ServerMonitorOptions struct {
|
||||
Admin *bool `help:"Is this an admin call?"`
|
||||
}
|
||||
|
||||
type ServerQgaSetPassword struct {
|
||||
ServerIdOptions
|
||||
|
||||
USERNAME string `help:"Which user to set password" json:"username"`
|
||||
PASSWORD string `help:"Password content" json:"password"`
|
||||
}
|
||||
|
||||
func (o *ServerQgaSetPassword) Params() (jsonutils.JSONObject, error) {
|
||||
return options.StructToParams(o)
|
||||
}
|
||||
|
||||
type ServerSaveImageOptions struct {
|
||||
ServerIdOptions
|
||||
IMAGE string `help:"Image name" json:"name"`
|
||||
|
||||
@@ -230,6 +230,7 @@ const (
|
||||
|
||||
ACT_ENCRYPTION = "encrypt"
|
||||
|
||||
ACT_CONSOLE = "console"
|
||||
ACT_WEBSSH = "webssh"
|
||||
ACT_CONSOLE = "console"
|
||||
ACT_WEBSSH = "webssh"
|
||||
ACT_SET_USER_PASSWORD = "set_user_password"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user