diff --git a/build/docker/Dockerfile.host-deployer b/build/docker/Dockerfile.host-deployer index 32646d30f9..19cbe0a2a2 100644 --- a/build/docker/Dockerfile.host-deployer +++ b/build/docker/Dockerfile.host-deployer @@ -1,4 +1,4 @@ -FROM registry.cn-beijing.aliyuncs.com/yunionio/host-deployer-base:1.4.6 +FROM registry.cn-beijing.aliyuncs.com/yunionio/host-deployer-base:1.4.7 MAINTAINER "Yaoqi Wan wanyaoqi@yunionyun.com" diff --git a/pkg/apis/compute/guest_const.go b/pkg/apis/compute/guest_const.go index e36642637b..60918a3dfa 100644 --- a/pkg/apis/compute/guest_const.go +++ b/pkg/apis/compute/guest_const.go @@ -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 ( diff --git a/pkg/apis/compute/host.go b/pkg/apis/compute/host.go index a0f7c0997e..4bedc7bd3c 100644 --- a/pkg/apis/compute/host.go +++ b/pkg/apis/compute/host.go @@ -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 { diff --git a/pkg/compute/guestdrivers/base.go b/pkg/compute/guestdrivers/base.go index 8fb785475b..42405925ec 100644 --- a/pkg/compute/guestdrivers/base.go +++ b/pkg/compute/guestdrivers/base.go @@ -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 } diff --git a/pkg/compute/guestdrivers/kvm.go b/pkg/compute/guestdrivers/kvm.go index caba863fbe..31091ec26f 100644 --- a/pkg/compute/guestdrivers/kvm.go +++ b/pkg/compute/guestdrivers/kvm.go @@ -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") diff --git a/pkg/compute/models/guestdrivers.go b/pkg/compute/models/guestdrivers.go index e74444d6dd..fafa30dbf1 100644 --- a/pkg/compute/models/guestdrivers.go +++ b/pkg/compute/models/guestdrivers.go @@ -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) diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index b73699bc0e..0b469c0f69 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -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 { diff --git a/pkg/compute/models/qemu_guest_agent.go b/pkg/compute/models/qemu_guest_agent.go index f56d07d88e..81a97925c5 100644 --- a/pkg/compute/models/qemu_guest_agent.go +++ b/pkg/compute/models/qemu_guest_agent.go @@ -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 { diff --git a/pkg/compute/tasks/guest_qga_reset_password_task.go b/pkg/compute/tasks/guest_qga_reset_password_task.go index 89949c7484..51277dfe79 100644 --- a/pkg/compute/tasks/guest_qga_reset_password_task.go +++ b/pkg/compute/tasks/guest_qga_reset_password_task.go @@ -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{} diff --git a/pkg/compute/tasks/guest_qga_restart_network_task.go b/pkg/compute/tasks/guest_qga_restart_network_task.go index 07549abbf6..9d3702dadf 100644 --- a/pkg/compute/tasks/guest_qga_restart_network_task.go +++ b/pkg/compute/tasks/guest_qga_restart_network_task.go @@ -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) } diff --git a/pkg/compute/tasks/guest_qga_sync_os_info_task.go b/pkg/compute/tasks/guest_qga_sync_os_info_task.go index 9e8db6012b..d93c1beb11 100644 --- a/pkg/compute/tasks/guest_qga_sync_os_info_task.go +++ b/pkg/compute/tasks/guest_qga_sync_os_info_task.go @@ -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) diff --git a/pkg/hostman/guestfs/core.go b/pkg/hostman/guestfs/core.go index 5041db2494..787aedfcce 100644 --- a/pkg/hostman/guestfs/core.go +++ b/pkg/hostman/guestfs/core.go @@ -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") } } diff --git a/pkg/hostman/guestfs/fsdriver/android.go b/pkg/hostman/guestfs/fsdriver/android.go index 38a07f0c60..ce9c36f63a 100644 --- a/pkg/hostman/guestfs/fsdriver/android.go +++ b/pkg/hostman/guestfs/fsdriver/android.go @@ -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" } diff --git a/pkg/hostman/guestfs/fsdriver/base.go b/pkg/hostman/guestfs/fsdriver/base.go index 24dfb121db..c89e2a60f9 100644 --- a/pkg/hostman/guestfs/fsdriver/base.go +++ b/pkg/hostman/guestfs/fsdriver/base.go @@ -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 diff --git a/pkg/hostman/guestfs/fsdriver/esxi.go b/pkg/hostman/guestfs/fsdriver/esxi.go index 04012d36c5..5b859ce5f9 100644 --- a/pkg/hostman/guestfs/fsdriver/esxi.go +++ b/pkg/hostman/guestfs/fsdriver/esxi.go @@ -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 } diff --git a/pkg/hostman/guestfs/fsdriver/interface.go b/pkg/hostman/guestfs/fsdriver/interface.go index 47d6fd8631..0c44a0b801 100644 --- a/pkg/hostman/guestfs/fsdriver/interface.go +++ b/pkg/hostman/guestfs/fsdriver/interface.go @@ -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 diff --git a/pkg/hostman/guestfs/fsdriver/linux.go b/pkg/hostman/guestfs/fsdriver/linux.go index baaeb9dc12..3da19cc0e7 100644 --- a/pkg/hostman/guestfs/fsdriver/linux.go +++ b/pkg/hostman/guestfs/fsdriver/linux.go @@ -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) diff --git a/pkg/hostman/guestfs/fsdriver/linux_init.go b/pkg/hostman/guestfs/fsdriver/linux_init.go index 806b6c782a..63e1406180 100644 --- a/pkg/hostman/guestfs/fsdriver/linux_init.go +++ b/pkg/hostman/guestfs/fsdriver/linux_init.go @@ -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 +} diff --git a/pkg/hostman/guestfs/fsdriver/macos.go b/pkg/hostman/guestfs/fsdriver/macos.go index d051f36e75..396809fc8a 100644 --- a/pkg/hostman/guestfs/fsdriver/macos.go +++ b/pkg/hostman/guestfs/fsdriver/macos.go @@ -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) diff --git a/pkg/hostman/guestfs/fsdriver/utils.go b/pkg/hostman/guestfs/fsdriver/utils.go new file mode 100644 index 0000000000..ad6215f1bf --- /dev/null +++ b/pkg/hostman/guestfs/fsdriver/utils.go @@ -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 +} diff --git a/pkg/hostman/guestfs/fsdriver/windows.go b/pkg/hostman/guestfs/fsdriver/windows.go index f69391f676..973e4e1037 100644 --- a/pkg/hostman/guestfs/fsdriver/windows.go +++ b/pkg/hostman/guestfs/fsdriver/windows.go @@ -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") diff --git a/pkg/hostman/guestfs/sshpart/sshpart.go b/pkg/hostman/guestfs/sshpart/sshpart.go index e1a0382c95..7d8cb49c32 100644 --- a/pkg/hostman/guestfs/sshpart/sshpart.go +++ b/pkg/hostman/guestfs/sshpart/sshpart.go @@ -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 { diff --git a/pkg/hostman/guestman/guest-agent.go b/pkg/hostman/guestman/guest-agent.go index 3cc616f5eb..6505318104 100644 --- a/pkg/hostman/guestman/guest-agent.go +++ b/pkg/hostman/guestman/guest-agent.go @@ -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) { diff --git a/pkg/hostman/guestman/guesthandlers/guesthandler.go b/pkg/hostman/guestman/guesthandlers/guesthandler.go index 67a47c831d..5e3eb36cd8 100644 --- a/pkg/hostman/guestman/guesthandlers/guesthandler.go +++ b/pkg/hostman/guestman/guesthandlers/guesthandler.go @@ -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) { diff --git a/pkg/hostman/guestman/guesthelper.go b/pkg/hostman/guestman/guesthelper.go index 1ca8bc52a5..68e67f7ae4 100644 --- a/pkg/hostman/guestman/guesthelper.go +++ b/pkg/hostman/guestman/guesthelper.go @@ -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 diff --git a/pkg/hostman/guestman/guestman.go b/pkg/hostman/guestman/guestman.go index a4578706d8..c93fb9c2a1 100644 --- a/pkg/hostman/guestman/guestman.go +++ b/pkg/hostman/guestman/guestman.go @@ -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) { diff --git a/pkg/hostman/host_services.go b/pkg/hostman/host_services.go index 864e52ff56..8fd9245517 100644 --- a/pkg/hostman/host_services.go +++ b/pkg/hostman/host_services.go @@ -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() diff --git a/pkg/hostman/hostdeployer/apis/utils.go b/pkg/hostman/hostdeployer/apis/utils.go index 08a8ed1ebd..d3bc5560b6 100644 --- a/pkg/hostman/hostdeployer/apis/utils.go +++ b/pkg/hostman/hostdeployer/apis/utils.go @@ -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 +} diff --git a/pkg/hostman/hostdeployer/deployserver/deployserver.go b/pkg/hostman/hostdeployer/deployserver/deployserver.go index 6cd7137ac8..fc2a5893d0 100644 --- a/pkg/hostman/hostdeployer/deployserver/deployserver.go +++ b/pkg/hostman/hostdeployer/deployserver/deployserver.go @@ -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", diff --git a/pkg/hostman/hostinfo/hostinfo.go b/pkg/hostman/hostinfo/hostinfo.go index b255dcac4c..2d6e5aa2bd 100644 --- a/pkg/hostman/hostinfo/hostinfo.go +++ b/pkg/hostman/hostinfo/hostinfo.go @@ -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() } diff --git a/pkg/hostman/hostinfo/hostpinger/doc.go b/pkg/hostman/hostinfo/hostpinger/doc.go new file mode 100644 index 0000000000..d743ce04f1 --- /dev/null +++ b/pkg/hostman/hostinfo/hostpinger/doc.go @@ -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" diff --git a/pkg/hostman/hostinfo/hostpinger.go b/pkg/hostman/hostinfo/hostpinger/hostpinger.go similarity index 91% rename from pkg/hostman/hostinfo/hostpinger.go rename to pkg/hostman/hostinfo/hostpinger/hostpinger.go index 5febe744bc..5d530c933b 100644 --- a/pkg/hostman/hostinfo/hostpinger.go +++ b/pkg/hostman/hostinfo/hostpinger/hostpinger.go @@ -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) } diff --git a/pkg/hostman/hostutils/hostutils.go b/pkg/hostman/hostutils/hostutils.go index 053a07c2c6..7a394142df 100644 --- a/pkg/hostman/hostutils/hostutils.go +++ b/pkg/hostman/hostutils/hostutils.go @@ -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 { diff --git a/pkg/hostman/monitor/qga/part.go b/pkg/hostman/monitor/qga/part.go new file mode 100644 index 0000000000..36a493b1cd --- /dev/null +++ b/pkg/hostman/monitor/qga/part.go @@ -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 +} diff --git a/pkg/hostman/monitor/qga/qga.go b/pkg/hostman/monitor/qga/qga.go index eb9c68b8b4..bc1c408acc 100644 --- a/pkg/hostman/monitor/qga/qga.go +++ b/pkg/hostman/monitor/qga/qga.go @@ -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) } }