mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-19 02:37:24 +08:00
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:
@@ -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 (
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user