feat(regin,host,host-deployer): qga deploy and fsdriver (#20863)

- host-deployer add deploy qga support
- host add qga partition fsdriver
- qga set network use fsdriver
- hostpinger sync qga status
This commit is contained in:
wanyaoqi
2024-07-25 11:49:32 +08:00
committed by GitHub
parent fda7529b3f
commit 401cc40655
35 changed files with 1050 additions and 219 deletions
+2 -4
View File
@@ -234,10 +234,8 @@ const (
)
const (
QGA_STATUS_UNKNOWN = "unknown"
QGA_STATUS_EXCUTING = "executing"
QGA_STATUS_EXECUTE_FAILED = "execute_failed"
QGA_STATUS_AVAILABLE = "available"
QGA_STATUS_UNKNOWN = "unknown"
QGA_STATUS_AVAILABLE = "available"
)
const (
+2
View File
@@ -536,6 +536,8 @@ type SHostPingInput struct {
RootPartitionUsedCapacityMb int `json:"root_partition_used_capacity_mb"`
StorageStats []SHostStorageStat `json:"storage_stats"`
QgaRunningGuestIds []string `json:"qga_running_guests"`
}
type HostReserveCpusInput struct {
+1 -1
View File
@@ -507,7 +507,7 @@ func (self *SBaseGuestDriver) QgaRequestGuestInfoTask(ctx context.Context, userC
return nil, httperrors.ErrNotImplemented
}
func (self *SBaseGuestDriver) QgaRequestSetNetwork(ctx context.Context, userCred mcclient.TokenCredential, body jsonutils.JSONObject, host *models.SHost, guest *models.SGuest) (jsonutils.JSONObject, error) {
func (self *SBaseGuestDriver) QgaRequestSetNetwork(ctx context.Context, task taskman.ITask, body jsonutils.JSONObject, host *models.SHost, guest *models.SGuest) (jsonutils.JSONObject, error) {
return nil, httperrors.ErrNotImplemented
}
+2 -2
View File
@@ -1111,10 +1111,10 @@ func (self *SKVMGuestDriver) QgaRequestGuestInfoTask(ctx context.Context, userCr
return res, nil
}
func (self *SKVMGuestDriver) QgaRequestSetNetwork(ctx context.Context, userCred mcclient.TokenCredential, body jsonutils.JSONObject, host *models.SHost, guest *models.SGuest) (jsonutils.JSONObject, error) {
func (self *SKVMGuestDriver) QgaRequestSetNetwork(ctx context.Context, task taskman.ITask, body jsonutils.JSONObject, host *models.SHost, guest *models.SGuest) (jsonutils.JSONObject, error) {
url := fmt.Sprintf("%s/servers/%s/qga-set-network", host.ManagerUri, guest.Id)
httpClient := httputils.GetDefaultClient()
header := mcclient.GetTokenHeaders(userCred)
header := task.GetTaskRequestHeader()
_, res, err := httputils.JSONRequest(httpClient, ctx, "POST", url, header, body, false)
if err != nil {
return nil, errors.Wrap(err, "host request")
+1 -1
View File
@@ -239,7 +239,7 @@ type IGuestDriver interface {
QgaRequestSetUserPassword(ctx context.Context, task taskman.ITask, host *SHost, guest *SGuest, input *api.ServerQgaSetPasswordInput) error
RequestQgaCommand(ctx context.Context, userCred mcclient.TokenCredential, body jsonutils.JSONObject, host *SHost, guest *SGuest) (jsonutils.JSONObject, error)
QgaRequestGuestInfoTask(ctx context.Context, userCred mcclient.TokenCredential, body jsonutils.JSONObject, host *SHost, guest *SGuest) (jsonutils.JSONObject, error)
QgaRequestSetNetwork(ctx context.Context, userCred mcclient.TokenCredential, body jsonutils.JSONObject, host *SHost, guest *SGuest) (jsonutils.JSONObject, error)
QgaRequestSetNetwork(ctx context.Context, task taskman.ITask, body jsonutils.JSONObject, host *SHost, guest *SGuest) (jsonutils.JSONObject, error)
QgaRequestGetNetwork(ctx context.Context, userCred mcclient.TokenCredential, body jsonutils.JSONObject, host *SHost, guest *SGuest) (jsonutils.JSONObject, error)
QgaRequestGetOsInfo(ctx context.Context, userCred mcclient.TokenCredential, body jsonutils.JSONObject, host *SHost, guest *SGuest) (jsonutils.JSONObject, error)
+14
View File
@@ -4662,6 +4662,19 @@ func (hh *SHost) PerformPing(ctx context.Context, userCred mcclient.TokenCredent
}
hh.SetMetadata(ctx, "root_partition_used_capacity_mb", input.RootPartitionUsedCapacityMb, userCred)
hh.SetMetadata(ctx, "memory_used_mb", input.MemoryUsedMb, userCred)
guests, _ := hh.GetGuests()
for _, guest := range guests {
if utils.IsInStringArray(guest.Id, input.QgaRunningGuestIds) {
if guest.QgaStatus != api.QGA_STATUS_AVAILABLE {
guest.UpdateQgaStatus(api.QGA_STATUS_AVAILABLE)
}
} else {
if guest.QgaStatus != api.QGA_STATUS_UNKNOWN {
guest.UpdateQgaStatus(api.QGA_STATUS_UNKNOWN)
}
}
}
}
if hh.HostStatus != api.HOST_ONLINE {
hh.PerformOnline(ctx, userCred, query, nil)
@@ -6190,6 +6203,7 @@ func (hh *SHost) MarkGuestUnknown(ctx context.Context, userCred mcclient.TokenCr
guests, _ := hh.GetGuests()
for _, guest := range guests {
guest.SetStatus(ctx, userCred, api.VM_UNKNOWN, "host offline")
guest.UpdateQgaStatus(api.QGA_STATUS_UNKNOWN)
}
guests2 := hh.GetGuestsBackupOnThisHost()
for _, guest := range guests2 {
-1
View File
@@ -59,7 +59,6 @@ func (self *SGuest) PerformQgaSetPassword(
return nil, err
}
self.SetStatus(ctx, userCred, api.VM_QGA_SET_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 {
@@ -46,7 +46,6 @@ func (self *SGuestQgaBaseTask) guestPing(ctx context.Context, guest *models.SGue
func (self *SGuestQgaBaseTask) taskFailed(ctx context.Context, guest *models.SGuest, reason string) {
guest.SetStatus(ctx, self.UserCred, api.VM_QGA_EXEC_COMMAND_FAILED, reason)
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))
@@ -94,7 +93,6 @@ func (self *GuestQgaSetPasswordTask) OnQgaGuestPingFailed(ctx context.Context, g
func (self *GuestQgaSetPasswordTask) OnQgaSetUserPassword(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
guest.SetStatus(ctx, self.UserCred, api.VM_RUNNING, "on qga set user password success")
guest.UpdateQgaStatus(api.QGA_STATUS_AVAILABLE)
db.OpsLog.LogEvent(guest, db.ACT_SET_USER_PASSWORD, "", self.UserCred)
input := &api.ServerQgaSetPasswordInput{}
@@ -18,6 +18,7 @@ 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"
@@ -48,20 +49,10 @@ func (self *GuestQgaRestartNetworkTask) OnRestartNetwork(ctx context.Context, gu
inBlockStream, _ := self.Params.Bool("in_block_stream")
_, err := self.requestSetNetwork(ctx, guest, device, ipMask, gateway)
//the first set maybe fail,if failed, try again
if err != nil {
logclient.AddActionLogWithStartable(self, guest, logclient.ACT_RESTART_NETWORK, err, self.UserCred, false)
_, err = self.requestSetNetwork(ctx, guest, device, ipMask, gateway)
if err != nil {
logclient.AddActionLogWithStartable(self, guest, logclient.ACT_RESTART_NETWORK, err, self.UserCred, false)
self.taskFailed(ctx, guest, prevIp, inBlockStream, err)
return
}
self.taskFailed(ctx, guest, prevIp, inBlockStream, err)
}
logclient.AddActionLogWithStartable(self, guest, logclient.ACT_QGA_NETWORK_SUCCESS, "qga restart network success", self.UserCred, true)
guest.UpdateQgaStatus(api.QGA_STATUS_AVAILABLE)
guest.StartSyncstatus(ctx, self.UserCred, "")
self.SetStageComplete(ctx, nil)
}
func (self *GuestQgaRestartNetworkTask) requestSetNetwork(ctx context.Context, guest *models.SGuest, device string, ipMask string, gateway string) (jsonutils.JSONObject, error) {
@@ -82,12 +73,25 @@ func (self *GuestQgaRestartNetworkTask) requestSetNetwork(ctx context.Context, g
if err != nil {
return nil, err
}
return drv.QgaRequestSetNetwork(ctx, self.UserCred, jsonutils.Marshal(inputQgaNet), host, guest)
self.SetStage("OnSetNetwork", nil)
return drv.QgaRequestSetNetwork(ctx, self, jsonutils.Marshal(inputQgaNet), host, guest)
}
func (self *GuestQgaRestartNetworkTask) OnSetNetwork(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
logclient.AddActionLogWithStartable(self, guest, logclient.ACT_QGA_NETWORK_SUCCESS, "qga restart network success", self.UserCred, true)
guest.StartSyncstatus(ctx, self.UserCred, "")
self.SetStageComplete(ctx, nil)
}
func (self *GuestQgaRestartNetworkTask) OnSetNetworkFailed(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
prevIp, _ := self.Params.GetString("prev_ip")
inBlockStream, _ := self.Params.Bool("in_block_stream")
self.taskFailed(ctx, guest, prevIp, inBlockStream, errors.Errorf(data.String()))
}
func (self *GuestQgaRestartNetworkTask) taskFailed(ctx context.Context, guest *models.SGuest, prevIp string, inBlockStream bool, err error) {
guest.SetStatus(ctx, self.GetUserCred(), api.VM_QGA_SET_NETWORK_FAILED, err.Error())
guest.UpdateQgaStatus(api.QGA_STATUS_EXECUTE_FAILED)
logclient.AddActionLogWithStartable(self, guest, logclient.ACT_RESTART_NETWORK, jsonutils.NewString(err.Error()), self.UserCred, false)
self.SetStageFailed(ctx, nil)
}
@@ -125,7 +125,6 @@ func (self *GuestQgaSyncOsInfoTask) updateOsInfo(ctx context.Context, guest *mod
func (self *GuestQgaSyncOsInfoTask) taskFailed(ctx context.Context, guest *models.SGuest, reason string) {
guest.SetStatus(ctx, self.UserCred, api.VM_QGA_EXEC_COMMAND_FAILED, reason)
guest.UpdateQgaStatus(api.QGA_STATUS_EXECUTE_FAILED)
db.OpsLog.LogEvent(guest, db.ACT_SYNC_OS_INFO_FAIL, reason, self.UserCred)
logclient.AddActionLogWithContext(ctx, guest, logclient.ACT_SET_USER_PASSWORD, reason, self.UserCred, false)
self.SetStageFailed(ctx, jsonutils.NewString(reason))
@@ -133,7 +132,6 @@ func (self *GuestQgaSyncOsInfoTask) taskFailed(ctx context.Context, guest *model
func (self *GuestQgaSyncOsInfoTask) OnUpdateOsInfoComplete(ctx context.Context, guest *models.SGuest, osInput api.ServerSetOSInfoInput) {
guest.SetStatus(ctx, self.UserCred, api.VM_RUNNING, "on qga set user password success")
guest.UpdateQgaStatus(api.QGA_STATUS_AVAILABLE)
db.OpsLog.LogEvent(guest, db.ACT_SYNC_OS_INFO, jsonutils.Marshal(osInput), self.UserCred)
logclient.AddActionLogWithContext(ctx, guest, logclient.ACT_SET_USER_PASSWORD, jsonutils.Marshal(osInput), self.UserCred, false)
self.SetStageComplete(ctx, nil)
+4 -1
View File
@@ -132,8 +132,11 @@ func DoDeployGuestFs(rootfs fsdriver.IRootFsDriver, guestDesc *deployapi.GuestDe
}
if guestDesc.Hypervisor == comapi.HYPERVISOR_KVM {
if err := rootfs.DeployQgaService(partition); err != nil {
return nil, errors.Wrap(err, "DeployQgaService")
}
if err := rootfs.DeployQgaBlackList(partition); err != nil {
return nil, fmt.Errorf("DeployQgaBlackList: %v", err)
return nil, errors.Wrap(err, "DeployQgaBlackList")
}
}
-4
View File
@@ -90,10 +90,6 @@ func (m *sBaseAndroidRootFs) DeployHosts(part IDiskPartition, hn, domain string,
return nil
}
func (m *sBaseAndroidRootFs) DeployQgaBlackList(part IDiskPartition) error {
return nil
}
func (m *sBaseAndroidRootFs) GetOs() string {
return "Android"
}
+8
View File
@@ -148,6 +148,14 @@ func (r *sGuestRootFsDriver) AllowAdminLogin() bool {
return true
}
func (m *sGuestRootFsDriver) DeployQgaBlackList(part IDiskPartition) error {
return nil
}
func (r *sGuestRootFsDriver) DeployQgaService(part IDiskPartition) error {
return nil
}
const (
modeAuthorizedKeysRWX = syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IXUSR
modeAuthorizedKeysRW = syscall.S_IRUSR | syscall.S_IWUSR
-4
View File
@@ -64,10 +64,6 @@ func (m *SEsxiRootFs) DeployHostname(part IDiskPartition, hostname, domain strin
return nil
}
func (m *SEsxiRootFs) DeployQgaBlackList(part IDiskPartition) error {
return nil
}
func (m *SEsxiRootFs) DeployHosts(part IDiskPartition, hn, domain string, ips []string) error {
return nil
}
@@ -76,6 +76,7 @@ type IRootFsDriver interface {
DeployHostname(part IDiskPartition, hn, domain string) error
DeployHosts(part IDiskPartition, hn, domain string, ips []string) error
DeployQgaBlackList(part IDiskPartition) error
DeployQgaService(part IDiskPartition) error
DeployNetworkingScripts(IDiskPartition, []*types.SServerNic) error
DeployStandbyNetworkingScripts(part IDiskPartition, nics, nicsStandby []*types.SServerNic) error
DeployUdevSubsystemScripts(IDiskPartition) error
+41 -37
View File
@@ -51,6 +51,9 @@ const (
YUNIONROOT_USER = "cloudroot"
TELEGRAF_BINARY_PATH = "/opt/yunion/bin/telegraf"
SUPERVISE_BINARY_PATH = "/opt/yunion/bin/supervise"
QGA_BINARY_PATH = "/opt/yunion/bin/qemu-ga"
QGA_WIN_MSI_INSTALLER_PATH = "/opt/yunion/bin/qemu-ga-x86_64.msi"
)
var (
@@ -107,6 +110,44 @@ func getHostname(hostname, domain string) string {
}
}
func (l *sLinuxRootFs) DeployQgaService(rootFs IDiskPartition) error {
qemuGuestAgentPath := "/usr/bin/qemu-ga"
if rootFs.Exists(qemuGuestAgentPath, false) {
// qemu-ga has been installed
return nil
}
output, err := procutils.NewCommand("cp", "-f",
QGA_BINARY_PATH, path.Join(rootFs.GetMountPath(), qemuGuestAgentPath)).Output()
if err != nil {
return errors.Wrapf(err, "cp qga binary failed %s", output)
}
if l.isSupportSystemd() {
udevPath := "/etc/udev/rules.d/"
if rootFs.Exists(udevPath, false) {
rules := rootFs.ListDir(udevPath, false)
for _, rule := range rules {
if strings.Index(rule, "qemu-guest-agent.rules") > 0 {
rootFs.Remove(path.Join(udevPath, rule), false)
}
}
qgaRules := `SUBSYSTEM=="virtio-ports", ATTR{name}=="org.qemu.guest_agent.0", \
TAG+="systemd" ENV{SYSTEMD_WANTS}="qemu-guest-agent.service"` + "\n"
if err := rootFs.FilePutContents(path.Join(udevPath, "99-qemu-guest-agent.rules"), qgaRules, false, false); err != nil {
return err
}
}
if err := l.InstallQemuGuestAgentSystemd(); err != nil {
return errors.Wrap(err, "qga InstallQemuGuestAgentSystemd")
}
} else {
initCmd := qemuGuestAgentPath
if err := l.installCrond(initCmd); err != nil {
return errors.Wrap(err, "qga installCrond")
}
}
return nil
}
func (l *sLinuxRootFs) DeployQgaBlackList(rootFs IDiskPartition) error {
var modeRwxOwner = syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IXUSR
var qgaConfDir = "/etc/sysconfig"
@@ -225,43 +266,6 @@ func (l *sLinuxRootFs) DeployPublicKey(rootFs IDiskPartition, selUsr string, pub
return DeployAuthorizedKeys(rootFs, usrDir, pubkeys, false, false)
}
func (d *SCoreOsRootFs) DeployQgaBlackList(rootFs IDiskPartition) error {
var modeRwxOwner = syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IXUSR
var qgaConfDir = "/etc/sysconfig"
var etcSysconfigQemuga = path.Join(qgaConfDir, "qemu-ga")
if err := rootFs.Mkdir(qgaConfDir, modeRwxOwner, false); err != nil {
return errors.Wrap(err, "mkdir qga conf dir")
}
blackListContent := `# This is a systemd environment file, not a shell script.
# It provides settings for \"/lib/systemd/system/qemu-guest-agent.service\".
# Comma-separated blacklist of RPCs to disable, or empty list to enable all.
#
# You can get the list of RPC commands using \"qemu-ga --blacklist='?'\".
# There should be no spaces between commas and commands in the blacklist.
# BLACKLIST_RPC=guest-file-open,guest-file-close,guest-file-read,guest-file-write,guest-file-seek,guest-file-flush,guest-exec,guest-exec-status
# Fsfreeze hook script specification.
#
# FSFREEZE_HOOK_PATHNAME=/dev/null : disables the feature.
#
# FSFREEZE_HOOK_PATHNAME=/path/to/executable : enables the feature with the
# specified binary or shell script.
#
# FSFREEZE_HOOK_PATHNAME= : enables the feature with the
# default value (invoke \"qemu-ga --help\" to interrogate).
FSFREEZE_HOOK_PATHNAME=/etc/qemu-ga/fsfreeze-hook"
`
if rootFs.Exists(etcSysconfigQemuga, false) {
if err := rootFs.FilePutContents(etcSysconfigQemuga, blackListContent, false, false); err != nil {
return errors.Wrap(err, "etcSysconfigQemuga error")
}
}
return nil
}
func (l *sLinuxRootFs) DeployYunionroot(rootFs IDiskPartition, pubkeys *deployapi.SSHKeys, isInit, enableCloudInit bool) error {
if !consts.AllowVmSELinux() {
l.DisableSelinux(rootFs)
@@ -88,3 +88,26 @@ WantedBy=multi-user.target
return nil
}
func (d *sLinuxRootFs) InstallQemuGuestAgentSystemd() error {
var serviceName = "qemu-guest-agent.service"
var unitPath = fmt.Sprintf("%s/%s", unitDirPath, serviceName)
var unitContent = fmt.Sprintf(`[Unit]
Description=QEMU Guest Agent
BindsTo=dev-virtio\x2dports-org.qemu.guest_agent.0.device
After=dev-virtio\x2dports-org.qemu.guest_agent.0.device
[Service]
ExecStart=/usr/bin/qemu-ga
Restart=always
RestartSec=0
[Install]
`)
err := d.rootFs.FilePutContents(unitPath, unitContent, false, false)
if err != nil {
return errors.Wrap(err, "save qemu-guest-agent.service unit fail")
}
return nil
}
-4
View File
@@ -116,10 +116,6 @@ func (m *SMacOSRootFs) DeployHosts(part IDiskPartition, hn, domain string, ips [
return nil
}
func (m *SMacOSRootFs) DeployQgaBlackList(part IDiskPartition) error {
return nil
}
func (m *SMacOSRootFs) GetReleaseInfo(IDiskPartition) *deployapi.ReleaseInfo {
spath := "/System/Library/CoreServices/SystemVersion.plist"
sInfo, _ := m.rootFs.FileGetContents(spath, false)
+69
View File
@@ -0,0 +1,69 @@
package fsdriver
import (
"fmt"
"os"
"syscall"
"time"
)
// SFileInfo implements os.FileInfo interface
type SFileInfo struct {
name string
size int64
mode os.FileMode
isDir bool
stat *syscall.Stat_t
}
func NewFileInfo(name string, size int64, mode os.FileMode, isDir bool, stat *syscall.Stat_t) *SFileInfo {
return &SFileInfo{name, size, mode, isDir, stat}
}
func (info SFileInfo) Name() string {
return info.name
}
func (info SFileInfo) Size() int64 {
return info.size
}
func (info SFileInfo) Mode() os.FileMode {
return info.mode
}
func (info SFileInfo) IsDir() bool {
return info.isDir
}
func (info SFileInfo) ModTime() time.Time {
// TODO: impl
return time.Now()
}
func (info SFileInfo) Sys() interface{} {
return info.stat
}
func ModeStr2Bin(mode string) (uint32, error) {
table := []map[byte]uint32{
{'-': syscall.S_IRUSR, 'd': syscall.S_IFDIR, 'l': syscall.S_IFLNK},
{'r': syscall.S_IRUSR},
{'w': syscall.S_IWUSR},
{'x': syscall.S_IXUSR, 's': syscall.S_ISUID},
{'r': syscall.S_IRGRP},
{'w': syscall.S_IWGRP},
{'x': syscall.S_IXGRP, 's': syscall.S_ISGID},
{'r': syscall.S_IROTH},
{'w': syscall.S_IWOTH},
{'x': syscall.S_IXOTH},
}
if len(mode) != len(table) {
return 0, fmt.Errorf("Invalid mod %q", mode)
}
var ret uint32 = 0
for i := 0; i < len(table); i++ {
ret |= table[i][mode[i]]
}
return ret, nil
}
+24 -4
View File
@@ -49,6 +49,8 @@ const (
WIN_TELEGRAF_BINARY_PATH = "/opt/yunion/bin/telegraf.exe"
WIN_TELEGRAF_PATH = "/Program Files/Telegraf"
WIN_QGA_PATH = "/Program Files/Qemu-ga"
)
type SWindowsRootFs struct {
@@ -277,10 +279,6 @@ func (w *SWindowsRootFs) DeployHosts(part IDiskPartition, hn, domain string, ips
return w.rootFs.FilePutContents(ETC_HOSTS, hf.String(), false, true)
}
func (w *SWindowsRootFs) DeployQgaBlackList(part IDiskPartition) error {
return nil
}
func (w *SWindowsRootFs) DeployNetworkingScripts(rootfs IDiskPartition, nics []*types.SServerNic) error {
mainNic, err := netutils2.GetMainNicFromDeployApi(nics)
if err != nil {
@@ -583,6 +581,28 @@ func (l *SWindowsRootFs) IsResizeFsPartitionSupport() bool {
return true
}
func (w *SWindowsRootFs) DeployQgaService(part IDiskPartition) error {
if err := w.rootFs.Mkdir(WIN_QGA_PATH, syscall.S_IRUSR|syscall.S_IWUSR|syscall.S_IXUSR, true); err != nil {
return errors.Wrap(err, "mkdir qemu-ga path")
}
qgaInstallerPath := path.Join(w.rootFs.GetMountPath(), WIN_QGA_PATH, "qemu-ga-x86_64.msi")
output, err := procutils.NewCommand("cp", "-f", QGA_WIN_MSI_INSTALLER_PATH, qgaInstallerPath).Output()
if err != nil {
return errors.Wrapf(err, "cp qga installer failed %s", output)
}
bootScript := strings.Join([]string{
`start "" "%PROGRAMFILES%\Qemu-ga\qemu-ga-x86_64.msi"`,
}, "\r\n")
w.appendGuestBootScript("qemu-ga", bootScript)
return nil
}
func (w *SWindowsRootFs) DeployQgaBlackList(part IDiskPartition) error {
return nil
}
func (w *SWindowsRootFs) DeployTelegraf(config string) (bool, error) {
if err := w.rootFs.Mkdir(WIN_TELEGRAF_PATH, syscall.S_IRUSR|syscall.S_IWUSR|syscall.S_IXUSR, true); err != nil {
return false, errors.Wrap(err, "mkdir telegraf path")
+8 -65
View File
@@ -485,7 +485,7 @@ func (p *SSHPartition) osStat(sPath string) (os.FileInfo, error) {
dat := regexp.MustCompile(`\s+`).Split(strings.TrimSpace(line), -1)
if len(dat) > 7 && ((dat[2][0] != 'l' && dat[len(dat)-1] == sPath) ||
(dat[2][0] == 'l' && dat[len(dat)-3] == sPath)) {
stMode, err := modeStr2Bin(dat[2])
stMode, err := fsdriver.ModeStr2Bin(dat[2])
if err != nil {
return nil, err
}
@@ -493,81 +493,24 @@ func (p *SSHPartition) osStat(sPath string) (os.FileInfo, error) {
stUid, _ := strconv.Atoi(dat[4])
stGid, _ := strconv.Atoi(dat[5])
stSize, _ := strconv.Atoi(dat[6])
info := &sFileInfo{
name: sPath,
size: int64(stSize),
mode: os.FileMode(stMode),
isDir: dat[2][0] == 'd',
stat: &syscall.Stat_t{
info := fsdriver.NewFileInfo(
sPath,
int64(stSize),
os.FileMode(stMode),
dat[2][0] == 'd',
&syscall.Stat_t{
Ino: uint64(stIno),
Uid: uint32(stUid),
Gid: uint32(stGid),
Size: int64(stSize),
},
}
)
return info, nil
}
}
return nil, fmt.Errorf("Can't stat for path %s", sPath)
}
func modeStr2Bin(mode string) (uint32, error) {
table := []map[byte]uint32{
{'-': syscall.S_IRUSR, 'd': syscall.S_IFDIR, 'l': syscall.S_IFLNK},
{'r': syscall.S_IRUSR},
{'w': syscall.S_IWUSR},
{'x': syscall.S_IXUSR, 's': syscall.S_ISUID},
{'r': syscall.S_IRGRP},
{'w': syscall.S_IWGRP},
{'x': syscall.S_IXGRP, 's': syscall.S_ISGID},
{'r': syscall.S_IROTH},
{'w': syscall.S_IWOTH},
{'x': syscall.S_IXOTH},
}
if len(mode) != len(table) {
return 0, fmt.Errorf("Invalid mod %q", mode)
}
var ret uint32 = 0
for i := 0; i < len(table); i++ {
ret |= table[i][mode[i]]
}
return ret, nil
}
// sFileInfo implements os.FileInfo interface
type sFileInfo struct {
name string
size int64
mode os.FileMode
isDir bool
stat *syscall.Stat_t
}
func (info sFileInfo) Name() string {
return info.name
}
func (info sFileInfo) Size() int64 {
return info.size
}
func (info sFileInfo) Mode() os.FileMode {
return info.mode
}
func (info sFileInfo) IsDir() bool {
return info.isDir
}
func (info sFileInfo) ModTime() time.Time {
// TODO: impl
return time.Now()
}
func (info sFileInfo) Sys() interface{} {
return info.stat
}
func (p *SSHPartition) Stat(sPath string, caseInsensitive bool) os.FileInfo {
sPath = p.GetLocalPath(sPath, caseInsensitive)
if len(sPath) == 0 {
+18 -10
View File
@@ -20,6 +20,7 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
"yunion.io/x/onecloud/pkg/hostman/monitor"
"yunion.io/x/onecloud/pkg/httperrors"
)
@@ -122,26 +123,33 @@ func (m *SGuestManager) QgaGuestInfoTask(sid string) (string, error) {
return "", errors.Errorf("qga unfinished last cmd, is qga unavailable?")
}
func (m *SGuestManager) QgaSetNetwork(netmod *monitor.NetworkModify, sid string, execTimeout int) (string, error) {
guest, err := m.checkAndInitGuestQga(sid)
func (m *SGuestManager) QgaSetNetwork(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
input := params.(*SQgaGuestSetNetwork)
netmod := &monitor.NetworkModify{
Device: input.Device,
Ipmask: input.Ipmask,
Gateway: input.Gateway,
}
//func (m *SGuestManager) QgaSetNetwork(netmod *monitor.NetworkModify, sid string, execTimeout int) (string, error) {
guest, err := m.checkAndInitGuestQga(input.Sid)
if err != nil {
return "", err
return nil, err
}
var res []byte
if guest.guestAgent.TryLock() {
defer guest.guestAgent.Unlock()
if execTimeout > 0 {
guest.guestAgent.SetTimeout(execTimeout)
if input.Timeout > 0 {
guest.guestAgent.SetTimeout(input.Timeout)
defer guest.guestAgent.ResetTimeout()
}
err = guest.guestAgent.QgaSetNetwork(netmod)
err = guest.guestAgent.QgaSetNetwork(netmod, deployapi.GuestNicsToServerNics(guest.Desc.Nics))
if err != nil {
return "", errors.Wrapf(err, "modify %s network failed", netmod.Device)
return nil, errors.Wrapf(err, "modify %s network failed", netmod.Device)
}
return string(res), nil
return nil, nil
}
return "", errors.Errorf("qga unfinished last cmd, is qga unavailable?")
return nil, errors.Errorf("qga unfinished last cmd, is qga unavailable?")
}
func (m *SGuestManager) QgaGetNetwork(sid string) (string, error) {
@@ -922,7 +922,6 @@ func qgaGetNetwork(ctx context.Context, userCred mcclient.TokenCredential, sid s
}
func qgaSetNetwork(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
gm := guestman.GetGuestManager()
input := computeapi.ServerQgaSetNetworkInput{}
err := body.Unmarshal(&input)
if err != nil {
@@ -938,12 +937,15 @@ func qgaSetNetwork(ctx context.Context, userCred mcclient.TokenCredential, sid s
return nil, httperrors.NewMissingParameterError("gateway")
}
qgaNetMod := &monitor.NetworkModify{
hostutils.DelayTask(ctx, guestman.GetGuestManager().QgaSetNetwork, &guestman.SQgaGuestSetNetwork{
Sid: sid,
Timeout: input.Timeout,
Device: input.Device,
Ipmask: input.Ipmask,
Gateway: input.Gateway,
}
return gm.QgaSetNetwork(qgaNetMod, sid, input.Timeout)
})
return nil, nil
}
func qgaGetOsInfo(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
+8
View File
@@ -193,6 +193,14 @@ type SQgaGuestSetPassword struct {
Sid string
}
type SQgaGuestSetNetwork struct {
Timeout int
Sid string
Device string
Ipmask string
Gateway string
}
type CpuSetCounter struct {
Nodes []*NumaNode
NumaEnabled bool
+21
View File
@@ -492,6 +492,27 @@ func (m *SGuestManager) ShutdownServers() {
})
}
func (m *SGuestManager) GetQgaRunningGuests() []string {
qgaRunningGuestIds := []string{}
m.Servers.Range(func(k, v interface{}) bool {
guest := v.(*SKVMGuestInstance)
if !guest.IsRunning() {
return true
}
if guest.guestAgent.TryLock() {
defer guest.guestAgent.Unlock()
err := guest.guestAgent.GuestPing(1)
if err == nil {
qgaRunningGuestIds = append(qgaRunningGuestIds, guest.Id)
}
}
return true
})
return qgaRunningGuestIds
}
func (m *SGuestManager) GetGuestNicDesc(
mac, ip, port, bridge string, isCandidate bool,
) (*desc.SGuestDesc, *desc.SGuestNetwork) {
+11
View File
@@ -24,6 +24,7 @@ import (
common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
"yunion.io/x/onecloud/pkg/cloudcommon/service"
"yunion.io/x/onecloud/pkg/hostman/downloader"
"yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver"
"yunion.io/x/onecloud/pkg/hostman/guestman"
"yunion.io/x/onecloud/pkg/hostman/guestman/desc"
"yunion.io/x/onecloud/pkg/hostman/guestman/guesthandlers"
@@ -31,6 +32,7 @@ import (
"yunion.io/x/onecloud/pkg/hostman/hostdeployer/deployclient"
"yunion.io/x/onecloud/pkg/hostman/hosthandler"
"yunion.io/x/onecloud/pkg/hostman/hostinfo"
"yunion.io/x/onecloud/pkg/hostman/hostinfo/hostpinger"
"yunion.io/x/onecloud/pkg/hostman/hostmetrics"
"yunion.io/x/onecloud/pkg/hostman/hostutils"
"yunion.io/x/onecloud/pkg/hostman/metadata"
@@ -114,6 +116,12 @@ func (host *SHostService) RunService() {
// hostmetrics after guestmanager bootstrap
hostmetrics.Init()
hostmetrics.Start()
fsdriver.Init("")
hostPinger := hostpinger.NewHostPingTask(options.HostOptions.PingRegionInterval, hostInstance)
if hostPinger != nil {
go hostPinger.Start()
}
host.initHandlers(app)
@@ -140,6 +148,9 @@ func (host *SHostService) RunService() {
close(guestChan)
app_common.ServeForeverWithCleanup(app, &options.HostOptions.BaseOptions, func() {
if hostPinger != nil {
hostPinger.Stop()
}
hostinfo.Stop()
storageman.Stop()
hostmetrics.Stop()
+44
View File
@@ -17,6 +17,7 @@ package apis
import (
"encoding/json"
"yunion.io/x/cloudmux/pkg/apis/compute"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
@@ -186,3 +187,46 @@ func NewReleaseInfo(distro, version, arch string) *ReleaseInfo {
Arch: arch,
}
}
func GuestNicsToServerNics(nics []*desc.SGuestNetwork) []*types.SServerNic {
ret := make([]*types.SServerNic, len(nics))
for i := 0; i < len(nics); i++ {
routes := ""
if nics[i].Routes != nil {
routes, _ = nics[i].Routes.GetString()
}
ret[i] = &types.SServerNic{
Name: "",
Index: int(nics[i].Index),
Bridge: nics[i].Bridge,
Domain: nics[i].Domain,
Ip: nics[i].Ip,
Vlan: int(nics[i].Vlan),
Driver: nics[i].Driver,
Masklen: int(nics[i].Masklen),
Virtual: nics[i].Virtual,
Manual: nics[i].Manual != nil && *nics[i].Manual,
WireId: nics[i].WireId,
NetId: nics[i].NetId,
Mac: nics[i].Mac,
BandWidth: int(nics[i].Bw),
Dns: nics[i].Dns,
Net: nics[i].Net,
Interface: nics[i].Interface,
Gateway: nics[i].Gateway,
Ifname: nics[i].Ifname,
Routes: ConvertRoutes(routes),
NicType: compute.TNicType(nics[i].NicType),
LinkUp: nics[i].LinkUp,
Mtu: int16(nics[i].Mtu),
TeamWith: nics[i].TeamWith,
IsDefault: nics[i].IsDefault,
Ip6: nics[i].Ip6,
Masklen6: int(nics[i].Masklen6),
Gateway6: nics[i].Gateway6,
}
}
return ret
}
@@ -360,18 +360,33 @@ func (s *SDeployService) PrepareEnv() error {
return errors.Wrap(err, "nbd.Init")
}
} else {
cpuArch, err := procutils.NewCommand("uname", "-m").Output()
if err != nil {
return errors.Wrap(err, "get cpu architecture")
}
cpuArchStr := strings.TrimSpace(string(cpuArch))
// prepare for yunionos don't have but necessary files
out, err := procutils.NewCommand("mkdir", "-p", "/opt/yunion/bin/bundles").Output()
if err != nil {
return errors.Wrapf(err, "cp files failed %s", out)
}
for k, v := range map[string]string{
copyFiles := map[string]string{
"/usr/bin/chntpw.static": "/opt/yunion/bin/chntpw.static",
"/usr/bin/.chntpw.static.bin": "/opt/yunion/bin/.chntpw.static.bin",
"/usr/bin/bundles/chntpw.static": "/opt/yunion/bin/bundles/chntpw.static",
"/usr/bin/growpart": "/opt/yunion/bin/growpart",
"/usr/sbin/zerofree": "/opt/yunion/bin/zerofree",
} {
}
// x86_64 or aarch64
if cpuArchStr == qemu_kvm.OS_ARCH_AARCH64 {
copyFiles["/yunionos/aarch64/qemu-ga"] = fsdriver.QGA_BINARY_PATH
} else {
copyFiles["/yunionos/x86_64/qemu-ga"] = fsdriver.QGA_BINARY_PATH
copyFiles["/yunionos/x86_64/qemu-ga-x86_64.msi"] = fsdriver.QGA_WIN_MSI_INSTALLER_PATH
}
for k, v := range copyFiles {
out, err = procutils.NewCommand("cp", "-rf", k, v).Output()
if err != nil {
return errors.Wrapf(err, "cp files failed %s", out)
@@ -401,13 +416,8 @@ func (s *SDeployService) PrepareEnv() error {
return errors.Wrapf(err, "mkisofs failed %s", out)
}
cpuArch, err := procutils.NewCommand("uname", "-m").Output()
if err != nil {
return errors.Wrap(err, "get cpu architecture")
}
err = qemu_kvm.InitQemuDeployManager(
strings.TrimSpace(string(cpuArch)),
cpuArchStr,
DeployOption.DefaultQemuVersion,
DeployOption.EnableRemoteExecutor,
DeployOption.HugepagesOption == "native",
+3 -14
View File
@@ -82,9 +82,7 @@ type SHostInfo struct {
// registerCallback func()
stopped bool
isLoged bool
saved bool
pinger *SHostPingTask
saved bool
Cpu *SCPUInfo
Mem *SMemory
@@ -2192,7 +2190,7 @@ func (h *SHostInfo) onSucc() {
if err := h.save(); err != nil {
panic(err.Error())
}
h.StartPinger()
//h.StartPinger()
// if h.registerCallback != nil {
// h.registerCallback()
// }
@@ -2224,13 +2222,6 @@ func (h *SHostInfo) RemoveErrorType(errType string) {
delete(h.SysError, errType)
}
func (h *SHostInfo) StartPinger() {
h.pinger = NewHostPingTask(options.HostOptions.PingRegionInterval)
if h.pinger != nil {
go h.pinger.Start()
}
}
func (h *SHostInfo) save() error {
if h.saved {
return nil
@@ -2293,9 +2284,7 @@ func (h *SHostInfo) Keyword() string {
func (h *SHostInfo) stop() {
log.Infof("Host Info stop ...")
h.unregister()
if h.pinger != nil {
h.pinger.Stop()
}
for _, nic := range h.Nics {
nic.ExitCleanup()
}
+15
View 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 hostpinger // import "yunion.io/x/onecloud/pkg/hostman/hostinfo/hostpinger"
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package hostinfo
package hostpinger
import (
"context"
@@ -25,6 +25,7 @@ import (
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/hostman/guestman"
"yunion.io/x/onecloud/pkg/hostman/hostutils"
"yunion.io/x/onecloud/pkg/hostman/options"
"yunion.io/x/onecloud/pkg/hostman/storageman"
@@ -36,6 +37,7 @@ import (
type SHostPingTask struct {
interval int // second
running bool
host hostutils.IHost
lastStatAt time.Time
}
@@ -62,13 +64,14 @@ func NewCatalog() *SCatalog {
}
}
func NewHostPingTask(interval int) *SHostPingTask {
func NewHostPingTask(interval int, host hostutils.IHost) *SHostPingTask {
if interval <= 0 {
return nil
}
return &SHostPingTask{
interval: interval,
running: true,
host: host,
}
}
@@ -76,7 +79,7 @@ func (p *SHostPingTask) Start() {
log.Infof("Start host pinger ...")
var (
div = 1
hostId = Instance().GetHostId()
hostId = p.host.GetHostId()
err error
)
for {
@@ -113,6 +116,7 @@ func (p *SHostPingTask) payload() api.SHostPingInput {
memFree := int(info.Available / 1024 / 1024)
memUsed := memTotal - memFree
data.MemoryUsedMb = memUsed
data.QgaRunningGuestIds = guestman.GetGuestManager().GetQgaRunningGuests()
return data
}
@@ -141,7 +145,7 @@ func (p *SHostPingTask) ping(div int, hostId string) error {
return nil
}
Instance().OnCatalogChanged(cl)
p.host.OnCatalogChanged(cl)
} else {
log.Errorf("get catalog from res %s: %v", res.String(), err)
}
+2
View File
@@ -78,6 +78,8 @@ type IHost interface {
GetContainerRuntimeEndpoint() string
GetCRI() pod.CRI
GetContainerCPUMap() *pod.HostContainerCPUMap
OnCatalogChanged(catalog mcclient.KeystoneServiceCatalogV3)
}
func GetComputeSession(ctx context.Context) *mcclient.ClientSession {
+562
View File
@@ -0,0 +1,562 @@
package qga
import (
"bytes"
"encoding/base64"
"fmt"
"os"
"path"
"regexp"
"strconv"
"strings"
"syscall"
"time"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/utils"
"yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver"
)
var _ fsdriver.IDiskPartition = &QemuGuestAgentPartition{}
type QemuGuestAgentPartition struct {
agent *QemuGuestAgent
}
func NewQGAPartition(agent *QemuGuestAgent) *QemuGuestAgentPartition {
return &QemuGuestAgentPartition{
agent: agent,
}
}
func (qga *QemuGuestAgent) CommandWithTimeout(
cmdPath string, args, env []string, inputData string, captureOutput bool,
timeoutSecond int,
) (int, string, string, error) {
pid, err := qga.GuestExecCommand(cmdPath, args, env, inputData, captureOutput)
if err != nil {
return -1, "", "", errors.Wrap(err, "GuestExecCommand")
}
if timeoutSecond <= 0 {
timeoutSecond = QGA_EXEC_DEFAULT_WAIT_TIMEOUT
}
for i := 0; i < timeoutSecond; i++ {
execStatus, err := qga.GuestExecStatusCommand(pid.Pid)
if err != nil {
return -1, "", "", errors.Wrap(err, "GuestExecStatusCommand")
}
if !execStatus.Exited {
time.Sleep(time.Second)
} else {
if captureOutput {
var stdout, stderr string
if len(execStatus.OutData) > 0 {
stdoutb, err := base64.StdEncoding.DecodeString(execStatus.OutData)
if err != nil {
return -1, "", "", errors.Wrap(err, "base64.StdEncoding.DecodeString")
}
stdout = string(stdoutb)
}
if len(execStatus.ErrData) > 0 {
stderrb, err := base64.StdEncoding.DecodeString(execStatus.ErrData)
if err != nil {
return -1, "", "", errors.Wrap(err, "base64.StdEncoding.DecodeString")
}
stderr = string(stderrb)
}
return execStatus.Exitcode, stdout, stderr, nil
} else {
return execStatus.Exitcode, "", "", nil
}
}
}
return -1, "", "", errors.Errorf("QGA guest-exec wait process exit timeout after wait %d second", timeoutSecond)
}
func (qga *QemuGuestAgent) FileGetContents(path string) (string, error) {
fileno, err := qga.QgaFileOpen(path, "r")
if err != nil {
return "", err
}
defer func() {
if e := qga.QgaFileClose(fileno); e != nil {
log.Errorf("failed close path %s: %s", path, e)
}
}()
var buf bytes.Buffer
for {
content, eof, err := qga.QgaFileRead(fileno, -1)
if err != nil {
return "", err
}
log.Debugf("read %s", content)
if len(content) > 0 {
buf.Write(content)
}
if eof {
break
}
}
return buf.String(), nil
}
func (qga *QemuGuestAgent) FilePutContents(path, content string, modAppend bool) error {
var mode = "w+"
if modAppend {
mode = "a+"
}
fileno, err := qga.QgaFileOpen(path, mode)
if err != nil {
return err
}
defer func() {
if e := qga.QgaFileClose(fileno); e != nil {
log.Errorf("failed close path %s: %s", path, e)
}
}()
var buf = bytes.NewBufferString(content)
for {
log.Debugf("write %s", buf.String())
count, _, err := qga.QgaFileWrite(fileno, buf.String())
if err != nil {
return err
}
if count > 0 {
buf.Next(count)
} else {
if buf.Len() > 0 {
return errors.Errorf("qga file put content remaining %d", buf.Len())
} else {
break
}
}
}
return nil
}
func (p *QemuGuestAgentPartition) osPathExists(path string) (bool, error) {
retCode, _, _, err := p.agent.CommandWithTimeout("test", []string{"-e", path}, nil, "", false, -1)
if err != nil {
return false, errors.Wrap(err, "CommandWithTimeout")
}
if retCode == 0 {
return true, nil
}
retCode, _, _, err = p.agent.CommandWithTimeout("test", []string{"-L", path}, nil, "", false, -1)
if err != nil {
return false, errors.Wrap(err, "CommandWithTimeout")
}
return retCode == 0, nil
}
func (p *QemuGuestAgentPartition) osIsDir(path string) (bool, error) {
retCode, _, _, err := p.agent.CommandWithTimeout("test", []string{"-d", path}, nil, "", false, -1)
if err != nil {
return false, errors.Wrap(err, "CommandWithTimeout")
}
return retCode == 0, nil
}
func (p *QemuGuestAgentPartition) osListDir(path string) ([]string, error) {
if ok, err := p.osIsDir(path); err != nil {
return nil, err
} else if !ok {
return nil, errors.Errorf("Path %s is not dir", path)
}
retcode, stdout, stderr, err := p.agent.CommandWithTimeout("ls", []string{"-a", path}, nil, "", true, -1)
if err != nil {
return nil, errors.Wrapf(err, "command ls -a %s", path)
}
if retcode > 0 {
return nil, errors.Errorf("failed guest-exec ls -a %s: %s %s %d", path, stdout, stderr, retcode)
}
files := []string{}
strfiles := strings.Split(stdout, "\n")
for _, f := range strfiles {
f = strings.TrimSpace(f)
if !utils.IsInStringArray(f, []string{"", ".", ".."}) {
files = append(files, f)
}
}
return files, nil
}
func (p *QemuGuestAgentPartition) GetLocalPath(sPath string, caseInsensitive bool) string {
if sPath == "." {
sPath = ""
}
if !caseInsensitive {
return sPath
}
var fullPath = "/"
pathSegs := strings.Split(sPath, "/")
for _, seg := range pathSegs {
if len(seg) == 0 {
continue
}
var realSeg string
files, err := p.osListDir(fullPath)
if err != nil {
log.Errorf("List dir %s error: %v", sPath, err)
return ""
}
for _, f := range files {
if f == seg || (caseInsensitive && (strings.ToLower(f)) == strings.ToLower(seg)) {
realSeg = f
break
}
}
if len(realSeg) > 0 {
fullPath = path.Join(fullPath, realSeg)
} else {
return ""
}
}
log.Debugf("QGA GetLocalPath %s=>%s", sPath, fullPath)
return fullPath
}
func (p *QemuGuestAgentPartition) FileGetContents(sPath string, caseInsensitive bool) ([]byte, error) {
sPath = p.GetLocalPath(sPath, caseInsensitive)
return p.FileGetContentsByPath(sPath)
}
func (p *QemuGuestAgentPartition) FileGetContentsByPath(sPath string) ([]byte, error) {
res, err := p.agent.FileGetContents(sPath)
if err != nil {
return nil, err
}
return []byte(res), nil
}
func (p *QemuGuestAgentPartition) FilePutContents(sPath, content string, modAppend, caseInsensitive bool) error {
sFilePath := p.GetLocalPath(sPath, caseInsensitive)
if len(sFilePath) > 0 {
sPath = sFilePath
} else {
dirPath := p.GetLocalPath(path.Dir(sPath), caseInsensitive)
if len(dirPath) > 0 {
sPath = path.Join(dirPath, path.Base(sPath))
}
}
if len(sPath) > 0 {
return p.agent.FilePutContents(sPath, content, modAppend)
} else {
return errors.Errorf("Can't put content to empty Path")
}
}
func (p *QemuGuestAgentPartition) Exists(sPath string, caseInsensitive bool) bool {
sPath = p.GetLocalPath(sPath, caseInsensitive)
if len(sPath) > 0 {
exists, err := p.osPathExists(sPath)
if err != nil {
log.Errorf("QGA failed detect path exist %s", err)
}
return exists
}
return false
}
func (p *QemuGuestAgentPartition) Chown(sPath string, uid, gid int, caseInsensitive bool) error {
sPath = p.GetLocalPath(sPath, caseInsensitive)
if len(sPath) == 0 {
return errors.Errorf("Can't get local path: %s", sPath)
}
args := []string{fmt.Sprintf("%d.%d", uid, gid), sPath}
retCode, stdout, stderr, err := p.agent.CommandWithTimeout("chown", args, nil, "", true, -1)
if err != nil {
return errors.Wrap(err, "CommandWithTimeout")
}
if retCode != 0 {
return errors.Errorf("QGA chown failed %s %s, retCode %d", stdout, stderr, retCode)
}
return nil
}
func (p *QemuGuestAgentPartition) Chmod(sPath string, mode uint32, caseInsensitive bool) error {
sPath = p.GetLocalPath(sPath, caseInsensitive)
if sPath == "" {
return nil
}
modeStr := fmt.Sprintf("%o", mode&0777)
args := []string{modeStr, sPath}
retCode, stdout, stderr, err := p.agent.CommandWithTimeout("chmod", args, nil, "", true, -1)
if err != nil {
return errors.Wrap(err, "CommandWithTimeout")
}
if retCode != 0 {
return errors.Errorf("QGA chmod failed %s %s, retCode %d", stdout, stderr, retCode)
}
return nil
}
func (p *QemuGuestAgentPartition) CheckOrAddUser(user, homeDir string, isSys bool) (string, error) {
// check user
retCode, stdout, stderr, err := p.agent.CommandWithTimeout("/bin/cat", []string{"/etc/passwd"}, nil, "", true, -1)
if err != nil {
return "", errors.Wrap(err, "CommandWithTimeout")
}
if retCode != 0 {
return "", errors.Errorf("QGA cat passwd failed %s %s, retCode %d", stdout, stderr, retCode)
}
var exist = false
var realHomeDir = ""
lines := strings.Split(stdout, "\n")
for i := len(lines) - 1; i >= 0; i-- {
userInfos := strings.Split(strings.TrimSpace(lines[i]), ":")
if len(userInfos) < 6 {
continue
}
if userInfos[0] != user {
continue
}
exist = true
realHomeDir = userInfos[5]
break
}
if exist {
args := []string{"-R", "/", "-E", "-1", "-m", "0", "-M", "99999", "-I", "-1", user}
retCode, stdout, stderr, err = p.agent.CommandWithTimeout("chage", args, nil, "", true, -1)
if err != nil {
return "", errors.Wrap(err, "CommandWithTimeout")
}
if retCode != 0 && !strings.Contains(stderr, "not found") {
return "", errors.Errorf("failed chage %s %s", stdout, stderr)
}
if !p.Exists(realHomeDir, false) {
err = p.Mkdir(realHomeDir, 0700, false)
if err != nil {
return "", errors.Wrapf(err, "Mkdir %s", realHomeDir)
}
retCode, stdout, stderr, err = p.agent.CommandWithTimeout("chown", []string{user, realHomeDir}, nil, "", true, -1)
if err != nil {
return "", errors.Wrap(err, "CommandWithTimeout")
}
if retCode != 0 {
return "", errors.Errorf("failed chown %s %s: %s %s, retcode %d", user, realHomeDir, stdout, stderr, retCode)
}
}
return realHomeDir, nil
}
return path.Join(homeDir, user), p.userAdd(user, homeDir, isSys)
}
func (p *QemuGuestAgentPartition) userAdd(user, homeDir string, isSys bool) error {
args := []string{"-m", "-s", "/bin/bash", user}
if isSys {
args = append(args, "-r", "-e", "''", "-f", "'-1'", "-K", "'PASS_MAX_DAYS=-1'")
}
if len(homeDir) > 0 {
args = append(args, "-d", path.Join(homeDir, user))
}
retCode, stdout, stderr, err := p.agent.CommandWithTimeout("useradd", args, nil, "", true, -1)
if err != nil {
return errors.Wrap(err, "CommandWithTimeout")
}
if retCode != 0 {
return errors.Errorf("failed useradd %s: %s %s, retcode %d", user, stdout, stderr, retCode)
}
return nil
}
func (p *QemuGuestAgentPartition) Stat(sPath string, caseInsensitive bool) os.FileInfo {
sPath = p.GetLocalPath(sPath, caseInsensitive)
if len(sPath) == 0 {
return nil
}
args := []string{"-a", "-l", "-n", "-i", "-s", "-d", sPath}
retCode, stdout, stderr, err := p.agent.CommandWithTimeout("ls", args, nil, "", true, -1)
if err != nil {
log.Errorf("CommandWithTimeout %s", err)
return nil
}
if retCode != 0 {
log.Errorf("failed ls %s: %s %s, retcode %d", sPath, stdout, stderr, retCode)
return nil
}
ret := strings.Split(stdout, "\n")
for _, line := range ret {
dat := regexp.MustCompile(`\s+`).Split(strings.TrimSpace(line), -1)
if len(dat) > 7 && ((dat[2][0] != 'l' && dat[len(dat)-1] == sPath) ||
(dat[2][0] == 'l' && dat[len(dat)-3] == sPath)) {
stMode, err := fsdriver.ModeStr2Bin(dat[2])
if err != nil {
log.Errorf("ModeStr2Bin %s", err)
return nil
}
stIno, _ := strconv.Atoi(dat[0])
stUid, _ := strconv.Atoi(dat[4])
stGid, _ := strconv.Atoi(dat[5])
stSize, _ := strconv.Atoi(dat[6])
info := fsdriver.NewFileInfo(
sPath,
int64(stSize),
os.FileMode(stMode),
dat[2][0] == 'd',
&syscall.Stat_t{
Ino: uint64(stIno),
Uid: uint32(stUid),
Gid: uint32(stGid),
Size: int64(stSize),
},
)
return info
}
}
return nil
}
func (p *QemuGuestAgentPartition) Symlink(src, dst string, caseInsensitive bool) error {
odstDir := path.Dir(dst)
if err := p.Mkdir(odstDir, 0755, caseInsensitive); err != nil {
return errors.Wrapf(err, "Mkdir %s", odstDir)
}
if p.Exists(dst, caseInsensitive) {
p.Remove(dst, caseInsensitive)
}
odstDir = p.GetLocalPath(odstDir, caseInsensitive)
dst = path.Join(odstDir, path.Base(dst))
retCode, stdout, stderr, err := p.agent.CommandWithTimeout("ln", []string{"-s", src, dst}, nil, "", true, -1)
if err != nil {
return errors.Wrap(err, "CommandWithTimeout")
}
if retCode != 0 {
return errors.Errorf("failed ln -s %s %s: %s %s, retcode %d", src, dst, stdout, stderr, retCode)
}
return nil
}
func (p *QemuGuestAgentPartition) Passwd(account, password string, caseInsensitive bool) error {
return p.agent.GuestSetUserPassword(account, password, false)
}
func (p *QemuGuestAgentPartition) Mkdir(sPath string, mode int, caseInsensitive bool) error {
segs := strings.Split(sPath, "/")
sp := ""
pPath := p.GetLocalPath("/", caseInsensitive)
var err error
for _, s := range segs {
if len(s) > 0 {
sp = path.Join(sp, s)
vPath := p.GetLocalPath(sp, caseInsensitive)
if len(vPath) == 0 {
err = p.osMkdirP(path.Join(pPath, s), uint32(mode))
pPath = p.GetLocalPath(sp, caseInsensitive)
} else {
pPath = vPath
}
}
}
return err
}
func (p *QemuGuestAgentPartition) osMkdirP(dir string, mode uint32) error {
retCode, stdout, stderr, err := p.agent.CommandWithTimeout("mkdir", []string{"-p", dir}, nil, "", true, -1)
if err != nil {
return errors.Wrap(err, "CommandWithTimeout")
}
if retCode != 0 {
return errors.Errorf("mkdir -p %s: %s %s: retCode: %d", dir, stdout, stderr, retCode)
}
return p.Chmod(dir, mode, false)
}
func (p *QemuGuestAgentPartition) ListDir(sPath string, caseInsensitive bool) []string {
sPath = p.GetLocalPath(sPath, caseInsensitive)
if len(sPath) > 0 {
ret, err := p.osListDir(sPath)
if err != nil {
log.Errorf("list dir for %s: %v", sPath, err)
return nil
}
return ret
}
return nil
}
func (p *QemuGuestAgentPartition) Remove(sPath string, caseInsensitive bool) {
sPath = p.GetLocalPath(sPath, caseInsensitive)
if len(sPath) > 0 {
retCode, stdout, stderr, err := p.agent.CommandWithTimeout("rm", []string{sPath}, nil, "", true, -1)
if err != nil {
log.Errorf("remove %s: %s", sPath, err)
return
}
if retCode != 0 {
log.Errorf("remove %s: %s %s: retCode: %d", sPath, stdout, stderr, retCode)
return
}
}
}
func (p *QemuGuestAgentPartition) Cleandir(dir string, keepdir, caseInsensitive bool) error {
sPath := p.GetLocalPath(dir, caseInsensitive)
if len(sPath) > 0 {
retCode, stdout, stderr, err := p.agent.CommandWithTimeout("rm", []string{"-rf", sPath}, nil, "", true, -1)
if err != nil {
return errors.Wrapf(err, "remove -rf %s", sPath)
}
if retCode != 0 {
return errors.Wrapf(err, "remove -rf %s: %s %s: retCode: %d", sPath, stdout, stderr, retCode)
}
}
return nil
}
func (*QemuGuestAgentPartition) Zerofiles(dir string, caseInsensitive bool) error {
return nil
}
func (*QemuGuestAgentPartition) SupportSerialPorts() bool {
return false
}
func (*QemuGuestAgentPartition) GetPartDev() string {
return "QGA"
}
func (*QemuGuestAgentPartition) IsMounted() bool {
return true
}
func (*QemuGuestAgentPartition) Mount() bool {
return true
}
func (*QemuGuestAgentPartition) MountPartReadOnly() bool {
return false
}
func (*QemuGuestAgentPartition) Umount() error {
return nil
}
func (*QemuGuestAgentPartition) GetMountPath() string {
return ""
}
func (*QemuGuestAgentPartition) IsReadonly() bool {
return false
}
func (*QemuGuestAgentPartition) GetPhysicalPartitionType() string {
return ""
}
func (*QemuGuestAgentPartition) Zerofree() {
}
func (*QemuGuestAgentPartition) GenerateSshHostKeys() error {
return nil
}
+116 -33
View File
@@ -31,10 +31,15 @@ import (
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/cloudcommon/types"
"yunion.io/x/onecloud/pkg/hostman/guestfs"
"yunion.io/x/onecloud/pkg/hostman/monitor"
)
const QGA_DEFAULT_READ_TIMEOUT_SECOND int = 5
const (
QGA_DEFAULT_READ_TIMEOUT_SECOND int = 5
QGA_EXEC_DEFAULT_WAIT_TIMEOUT int = 5
)
type QemuGuestAgent struct {
id string
@@ -317,13 +322,12 @@ func (qga *QemuGuestAgent) QgaGuestGetOsInfo() (*GuestOsInfo, error) {
return resOsInfo, nil
}
func (qga *QemuGuestAgent) QgaFileOpen(path string) (int, error) {
//file open
func (qga *QemuGuestAgent) QgaFileOpen(path, mode string) (int, error) {
cmdFileOpen := &monitor.Command{
Execute: "guest-file-open",
Args: map[string]interface{}{
"path": path,
"mode": "w+",
"mode": mode,
},
}
rawResFileOpen, err := qga.execCmd(cmdFileOpen, true, -1)
@@ -337,7 +341,12 @@ func (qga *QemuGuestAgent) QgaFileOpen(path string) (int, error) {
return int(fileNum), nil
}
func (qga *QemuGuestAgent) QgaFileWrite(fileNum int, content string) error {
type GuestFileWrite struct {
Count int `json:"count"`
Eof bool `json:"eof"`
}
func (qga *QemuGuestAgent) QgaFileWrite(fileNum int, content string) (int, bool, error) {
contentEncode := base64.StdEncoding.EncodeToString([]byte(content))
//write shell to file
cmdFileWrite := &monitor.Command{
@@ -347,11 +356,53 @@ func (qga *QemuGuestAgent) QgaFileWrite(fileNum int, content string) error {
"buf-b64": contentEncode,
},
}
_, err := qga.execCmd(cmdFileWrite, true, -1)
rawResFileWrite, err := qga.execCmd(cmdFileWrite, true, -1)
if err != nil {
return err
return -1, false, err
}
return nil
resWrite := new(GuestFileWrite)
err = json.Unmarshal(*rawResFileWrite, resWrite)
if err != nil {
return -1, false, errors.Wrap(err, "unmarshal raw response")
}
return resWrite.Count, resWrite.Eof, nil
}
type GuestFileRead struct {
Count int `json:"count"`
BufB64 string `json:"buf-b64"`
Eof bool `json:"eof"`
}
func (qga *QemuGuestAgent) QgaFileRead(fileNum, readCount int) ([]byte, bool, error) {
cmdFileRead := &monitor.Command{
Execute: "guest-file-read",
}
args := map[string]interface{}{
"handle": fileNum,
}
// readCount: maximum number of bytes to read (default is 4KB, maximum is 48MB)
if readCount > 0 {
args["count"] = readCount
}
cmdFileRead.Args = args
rawResFileRead, err := qga.execCmd(cmdFileRead, true, -1)
if err != nil {
return nil, false, err
}
resReadInfo := new(GuestFileRead)
err = json.Unmarshal(*rawResFileRead, resReadInfo)
if err != nil {
return nil, false, errors.Wrap(err, "unmarshal raw response")
}
content, err := base64.StdEncoding.DecodeString(resReadInfo.BufB64)
if err != nil {
return nil, false, errors.Wrap(err, "failed decode base64")
}
return content, resReadInfo.Eof, nil
}
func (qga *QemuGuestAgent) QgaFileClose(fileNum int) error {
@@ -443,38 +494,66 @@ func (qga *QemuGuestAgent) QgaSetWindowsNetwork(qgaNetMod *monitor.NetworkModify
return nil
}
func (qga *QemuGuestAgent) QgaSetLinuxNetwork(qgaNetMod *monitor.NetworkModify) error {
pid, err := qga.GuestExecCommand("/sbin/dhclient", []string{"-r", qgaNetMod.Device}, []string{}, "", false)
if err != nil {
return errors.Wrap(err, "failed release dhcp current lease")
var NETWORK_RESTRT_SCRIPT = `#!/bin/bash
set -e
DEV=$1
if systemctl is-active --quiet NetworkManager.service; then
nmcli connection down $DEV && nmcli connection up $DEV
exit 0
fi
if command -v ifup &> /dev/null; then
ifdown $DEV && ifup $DEV
exit 0
fi
if command -v ifconfig &> /dev/null; then
ifconfig $DEV down && ifconfig $DEV up
exit 0
fi
if systemctl is-active --quiet network.service; then
systemctl restart network.service
exit 0
fi
if command -v ip &> /dev/null; then
ip link set $DEV down && ip link set $DEV up
exit 0
fi
echo "No valid method restart network device"
exit 1
`
func (qga *QemuGuestAgent) QgaRestartLinuxNetwork(qgaNetMod *monitor.NetworkModify) error {
scriptPath := "/tmp/qga_restart_network"
if err := qga.FilePutContents(scriptPath, NETWORK_RESTRT_SCRIPT, false); err != nil {
return errors.Wrap(err, "write qga_restart_network script")
}
for i := 0; i < 3; i++ {
// wait dhclient release current lease
time.Sleep(time.Millisecond * 100)
res, err := qga.GuestExecStatusCommand(pid.Pid)
if err != nil {
log.Errorf("failed get exec status %s", err)
continue
}
if res.Exited {
break
}
}
// flush ipv4 address
_, err = qga.GuestExecCommand("ifconfig", []string{qgaNetMod.Device, "0.0.0.0"}, []string{}, "", false)
retCode, stdout, stderr, err := qga.CommandWithTimeout("bash", []string{scriptPath, qgaNetMod.Device}, nil, "", true, 10)
if err != nil {
return errors.Wrap(err, "failed release dhcp current lease")
return errors.Wrap(err, "CommandWithTimeout")
}
_, err = qga.GuestExecCommand("/sbin/dhclient", []string{"-1", qgaNetMod.Device}, []string{}, "", false)
if err != nil {
return errors.Wrap(err, "failed request dhcp lease")
if retCode != 0 {
return errors.Errorf("QgaRestartLinuxNetwork failed: %s %s retcode %d", stdout, stderr, retCode)
}
return nil
}
func (qga *QemuGuestAgent) QgaSetNetwork(qgaNetMod *monitor.NetworkModify) error {
func (qga *QemuGuestAgent) qgaDeployNetworkConfigure(guestNics []*types.SServerNic) error {
qgaPart := NewQGAPartition(qga)
fsDriver, err := guestfs.DetectRootFs(qgaPart)
if err != nil {
return errors.Wrap(err, "qga DetectRootFs")
}
log.Infof("QGA %s DetectRootFs %s", qga.id, fsDriver.String())
return fsDriver.DeployNetworkingScripts(qgaPart, guestNics)
}
func (qga *QemuGuestAgent) QgaSetNetwork(qgaNetMod *monitor.NetworkModify, guestNics []*types.SServerNic) error {
//Getting information about the operating system
resOsInfo, err := qga.QgaGuestGetOsInfo()
if err != nil {
@@ -486,7 +565,11 @@ func (qga *QemuGuestAgent) QgaSetNetwork(qgaNetMod *monitor.NetworkModify) error
case "mswindows":
return qga.QgaSetWindowsNetwork(qgaNetMod)
default:
return qga.QgaSetLinuxNetwork(qgaNetMod)
// do deploy network configure
if err := qga.qgaDeployNetworkConfigure(guestNics); err != nil {
return errors.Wrap(err, "qgaDeployNetworkConfigure")
}
return qga.QgaRestartLinuxNetwork(qgaNetMod)
}
}