feat(region,host,host-deployer):use qga set network

This commit is contained in:
chentianxiang
2023-09-20 16:11:31 +08:00
parent 74017f84a2
commit 4fd11f2900
24 changed files with 743 additions and 29 deletions

View File

@@ -115,6 +115,8 @@ func init() {
cmd.Perform("qga-set-password", &options.ServerQgaSetPassword{})
cmd.Perform("qga-command", &options.ServerQgaCommand{})
cmd.Perform("qga-ping", &options.ServerQgaPing{})
cmd.Perform("qga-guest-info-task", &options.ServerQgaGuestInfoTask{})
cmd.Perform("qga-get-network", &options.ServerQgaGetNetwork{})
cmd.Perform("set-password", &options.ServerSetPasswordOptions{})
cmd.Perform("set-boot-index", &options.ServerSetBootIndexOptions{})
cmd.Perform("reset-nic-traffic-limit", &options.ServerNicTrafficLimitOptions{})

View File

@@ -167,6 +167,9 @@ const (
VM_QGA_COMMAND_EXECUTING = "qga_command_executing"
VM_QGA_EXEC_COMMAND_FAILED = "qga_exec_command_failed"
VM_QGA_SET_NETWORK = "qga_set_network"
VM_QGA_SET_NETWORK_FAILED = "qga_set_network_failed"
SHUTDOWN_STOP = "stop"
SHUTDOWN_TERMINATE = "terminate"

View File

@@ -985,11 +985,46 @@ type ServerQemuInfo struct {
Cmdline string `json:"cmdline"`
}
type IPAddress struct {
IPAddress string `json:"ip-address"`
IPAddressType string `json:"ip-address-type"`
Prefix int `json:"prefix"`
}
type IfnameDetail struct {
HardwareAddress string `json:"hardware-address"`
IPAddresses []IPAddress `json:"ip-addresses"`
Name string `json:"name"`
Statistics struct {
RxBytes int `json:"rx-bytes"`
RxDropped int `json:"rx-dropped"`
RxErrs int `json:"rx-errs"`
RxPackets int `json:"rx-packets"`
TxBytes int `json:"tx-bytes"`
TxDropped int `json:"tx-dropped"`
TxErrs int `json:"tx-errs"`
TxPackets int `json:"tx-packets"`
} `json:"statistics"`
}
type ServerQgaSetPasswordInput struct {
Username string
Password string
}
type ServerQgaGuestInfoTaskInput struct {
}
type ServerQgaSetNetworkInput struct {
ServerQgaTimeoutInput
Device string
Ipmask string
Gateway string
}
type ServerQgaGetNetworkInput struct {
}
type ServerQgaTimeoutInput struct {
// qga execute timeout millisecond
Timeout int

View File

@@ -504,6 +504,18 @@ func (drv *SBaseGuestDriver) QgaRequestSetUserPassword(ctx context.Context, task
return httperrors.ErrNotImplemented
}
func (self *SBaseGuestDriver) QgaRequestGuestInfoTask(ctx context.Context, userCred mcclient.TokenCredential, body jsonutils.JSONObject, host *models.SHost, guest *models.SGuest) (jsonutils.JSONObject, error) {
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) {
return nil, httperrors.ErrNotImplemented
}
func (self *SBaseGuestDriver) QgaRequestGetNetwork(ctx context.Context, userCred mcclient.TokenCredential, body jsonutils.JSONObject, host *models.SHost, guest *models.SGuest) (jsonutils.JSONObject, error) {
return nil, httperrors.ErrNotImplemented
}
func (drv *SBaseGuestDriver) RequestQgaCommand(ctx context.Context, userCred mcclient.TokenCredential, body jsonutils.JSONObject, host *models.SHost, guest *models.SGuest) (jsonutils.JSONObject, error) {
return nil, httperrors.ErrNotImplemented
}

View File

@@ -1052,6 +1052,39 @@ func (self *SKVMGuestDriver) QgaRequestGuestPing(ctx context.Context, header htt
return nil
}
func (self *SKVMGuestDriver) QgaRequestGuestInfoTask(ctx context.Context, userCred mcclient.TokenCredential, body jsonutils.JSONObject, host *models.SHost, guest *models.SGuest) (jsonutils.JSONObject, error) {
url := fmt.Sprintf("%s/servers/%s/qga-guest-info-task", host.ManagerUri, guest.Id)
httpClient := httputils.GetDefaultClient()
header := mcclient.GetTokenHeaders(userCred)
_, res, err := httputils.JSONRequest(httpClient, ctx, "POST", url, header, nil, false)
if err != nil {
return nil, errors.Wrap(err, "host request")
}
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) {
url := fmt.Sprintf("%s/servers/%s/qga-set-network", host.ManagerUri, guest.Id)
httpClient := httputils.GetDefaultClient()
header := mcclient.GetTokenHeaders(userCred)
_, res, err := httputils.JSONRequest(httpClient, ctx, "POST", url, header, body, false)
if err != nil {
return nil, errors.Wrap(err, "host request")
}
return res, nil
}
func (self *SKVMGuestDriver) QgaRequestGetNetwork(ctx context.Context, userCred mcclient.TokenCredential, body jsonutils.JSONObject, host *models.SHost, guest *models.SGuest) (jsonutils.JSONObject, error) {
url := fmt.Sprintf("%s/servers/%s/qga-get-network", host.ManagerUri, guest.Id)
httpClient := httputils.GetDefaultClient()
header := mcclient.GetTokenHeaders(userCred)
_, res, err := httputils.JSONRequest(httpClient, ctx, "POST", url, header, nil, false)
if err != nil {
return nil, errors.Wrap(err, "host request")
}
return res, nil
}
func (self *SKVMGuestDriver) QgaRequestSetUserPassword(ctx context.Context, task taskman.ITask, host *models.SHost, guest *models.SGuest, input *api.ServerQgaSetPasswordInput) error {
url := fmt.Sprintf("%s/servers/%s/qga-set-password", host.ManagerUri, guest.Id)
httpClient := httputils.GetDefaultClient()

View File

@@ -937,6 +937,22 @@ func (self *SGuest) StartRestartNetworkTask(ctx context.Context, userCred mcclie
return nil
}
func (self *SGuest) StartQgaRestartNetworkTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, device string, ipMask string, gateway string, prevIp string, inBlockStream bool) error {
data := jsonutils.NewDict()
data.Set("device", jsonutils.NewString(device))
data.Set("ip_mask", jsonutils.NewString(ipMask))
data.Set("gateway", jsonutils.NewString(gateway))
data.Set("prev_ip", jsonutils.NewString(prevIp))
data.Set("in_block_stream", jsonutils.NewBool(inBlockStream))
if task, err := taskman.TaskManager.NewTask(ctx, "GuestQgaRestartNetworkTask", self, userCred, data, parentTaskId, "", nil); err != nil {
log.Errorln(err)
return err
} else {
task.ScheduleRun(nil)
}
return nil
}
func (self *SGuest) startSyncTask(ctx context.Context, userCred mcclient.TokenCredential, firewallOnly bool, parentTaskId string, data *jsonutils.JSONDict) error {
if firewallOnly {
data.Add(jsonutils.JSONTrue, "fw_only")
@@ -948,15 +964,7 @@ func (self *SGuest) startSyncTask(ctx context.Context, userCred mcclient.TokenCr
}
func (self *SGuest) StartSyncTask(ctx context.Context, userCred mcclient.TokenCredential, firewallOnly bool, parentTaskId string) error {
data := jsonutils.NewDict()
if firewallOnly {
data.Add(jsonutils.JSONTrue, "fw_only")
} else if err := self.SetStatus(userCred, api.VM_SYNC_CONFIG, ""); err != nil {
log.Errorln(err)
return err
}
return self.doSyncTask(ctx, data, userCred, parentTaskId)
return self.startSyncTask(ctx, userCred, firewallOnly, parentTaskId, jsonutils.NewDict())
}
func (self *SGuest) StartSyncTaskWithoutSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, fwOnly bool, parentTaskId string) error {
@@ -2368,6 +2376,14 @@ func (self *SGuest) PerformChangeIpaddr(ctx context.Context, userCred mcclient.T
return nil, err
}
//Get the detailed description of the NIC
networkJsonDesc := ngn[0].getJsonDesc()
newIpAddr := networkJsonDesc.Ip
newMacAddr := networkJsonDesc.Mac
newMaskLen := networkJsonDesc.Masklen
newGateway := networkJsonDesc.Gateway
ipMask := fmt.Sprintf("%s/%d", newIpAddr, newMaskLen)
notes := jsonutils.NewDict()
if gn != nil {
notes.Add(jsonutils.NewString(gn.IpAddr), "prev_ip")
@@ -2383,12 +2399,38 @@ func (self *SGuest) PerformChangeIpaddr(ctx context.Context, userCred mcclient.T
if self.Hypervisor == api.HYPERVISOR_KVM && restartNetwork && (self.Status == api.VM_RUNNING || self.Status == api.VM_BLOCK_STREAM) {
taskData.Set("restart_network", jsonutils.JSONTrue)
taskData.Set("prev_ip", jsonutils.NewString(gn.IpAddr))
taskData.Set("prev_mac", jsonutils.NewString(newMacAddr))
net := ngn[0].GetNetwork()
taskData.Set("is_vpc_network", jsonutils.NewBool(net.isOneCloudVpcNetwork()))
taskData.Set("ip_mask", jsonutils.NewString(ipMask))
taskData.Set("gateway", jsonutils.NewString(newGateway))
if self.Status == api.VM_BLOCK_STREAM {
taskData.Set("in_block_stream", jsonutils.JSONTrue)
}
self.SetStatus(userCred, api.VM_RESTART_NETWORK, "restart network")
}
return nil, self.startSyncTask(ctx, userCred, true, "", taskData)
return nil, self.startSyncTask(ctx, userCred, false, "", taskData)
}
func (self *SGuest) GetIfNameByMac(ctx context.Context, userCred mcclient.TokenCredential, mac string) (string, error) {
//Find the network card according to the mac address, if it is empty, it means no network card is found
ifnameData, err := self.PerformQgaGetNetwork(ctx, userCred, nil, nil)
if err != nil {
return "", err
}
//Get the name of the network card
var parsedData []api.IfnameDetail
if err := ifnameData.Unmarshal(&parsedData); err != nil {
return "", err
}
var ifnameDevice string
//Finding a network card by its mac address
for _, detail := range parsedData {
if detail.HardwareAddress == mac {
ifnameDevice = detail.Name
}
}
return ifnameDevice, nil
}
func (self *SGuest) PerformDetachnetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerDetachnetworkInput) (jsonutils.JSONObject, error) {

View File

@@ -234,6 +234,9 @@ type IGuestDriver interface {
QgaRequestGuestPing(ctx context.Context, header http.Header, host *SHost, guest *SGuest, async bool, input *api.ServerQgaTimeoutInput) error
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)
QgaRequestGetNetwork(ctx context.Context, userCred mcclient.TokenCredential, body jsonutils.JSONObject, host *SHost, guest *SGuest) (jsonutils.JSONObject, error)
FetchMonitorUrl(ctx context.Context, guest *SGuest) string
RequestResetNicTrafficLimit(ctx context.Context, task taskman.ITask, host *SHost, guest *SGuest, input *api.ServerNicTrafficLimit) error

View File

@@ -103,3 +103,29 @@ func (self *SGuest) PerformQgaCommand(
host, _ := self.GetHost()
return self.GetDriver().RequestQgaCommand(ctx, userCred, jsonutils.Marshal(input), host, self)
}
func (self *SGuest) PerformQgaGuestInfoTask(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input *api.ServerQgaGuestInfoTaskInput,
) (jsonutils.JSONObject, error) {
if self.PowerStates != api.VM_POWER_STATES_ON {
return nil, httperrors.NewBadRequestError("can't use qga in vm status: %s", self.Status)
}
host, _ := self.GetHost()
return self.GetDriver().QgaRequestGuestInfoTask(ctx, userCred, nil, host, self)
}
func (self *SGuest) PerformQgaGetNetwork(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input *api.ServerQgaGetNetworkInput,
) (jsonutils.JSONObject, error) {
if self.PowerStates != api.VM_POWER_STATES_ON {
return nil, httperrors.NewBadRequestError("can't use qga in vm status: %s", self.Status)
}
host, _ := self.GetHost()
return self.GetDriver().QgaRequestGetNetwork(ctx, userCred, nil, host, self)
}

View File

@@ -0,0 +1,93 @@
// 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 tasks
import (
"context"
"yunion.io/x/jsonutils"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/compute/models"
"yunion.io/x/onecloud/pkg/util/logclient"
)
type GuestQgaRestartNetworkTask struct {
SGuestBaseTask
}
func init() {
taskman.RegisterTask(GuestQgaRestartNetworkTask{})
}
func (self *GuestQgaRestartNetworkTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
guest := obj.(*models.SGuest)
guest.SetStatus(self.UserCred, api.VM_QGA_SET_NETWORK, "")
self.OnRestartNetwork(ctx, guest)
}
func (self *GuestQgaRestartNetworkTask) OnRestartNetwork(ctx context.Context, guest *models.SGuest) {
device, _ := self.Params.GetString("device")
ipMask, _ := self.Params.GetString("ip_mask")
gateway, _ := self.Params.GetString("gateway")
prevIp, _ := self.Params.GetString("prev_ip")
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
}
}
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) {
host, err := guest.GetHost()
if err != nil {
self.taskFailed(ctx, guest, "", false, err)
return nil, err
}
inputQgaNet := &api.ServerQgaSetNetworkInput{
Device: device,
Ipmask: ipMask,
Gateway: gateway,
}
// if success, log network related information
logclient.AddActionLogWithStartable(self, guest, logclient.ACT_QGA_NETWORK_INPUT, inputQgaNet, self.UserCred, true)
return guest.GetDriver().QgaRequestSetNetwork(ctx, self.UserCred, jsonutils.Marshal(inputQgaNet), host, guest)
}
func (self *GuestQgaRestartNetworkTask) taskFailed(ctx context.Context, guest *models.SGuest, prevIp string, inBlockStream bool, err error) {
guest.SetStatus(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)
if prevIp != "" {
//use ansible to restart network
guest.StartRestartNetworkTask(ctx, self.UserCred, "", prevIp, inBlockStream)
}
self.SetStageFailed(ctx, nil)
}

View File

@@ -16,14 +16,19 @@ package tasks
import (
"context"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/compute/models"
"yunion.io/x/onecloud/pkg/compute/options"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcclient/modules/vpcagent"
"yunion.io/x/onecloud/pkg/util/logclient"
)
@@ -52,24 +57,8 @@ func (self *GuestSyncConfTask) OnInit(ctx context.Context, obj db.IStandaloneMod
func (self *GuestSyncConfTask) OnSyncComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
guest := obj.(*models.SGuest)
if fwOnly, _ := self.GetParams().Bool("fw_only"); fwOnly {
db.OpsLog.LogEvent(guest, db.ACT_SYNC_CONF, nil, self.UserCred)
if restart, _ := self.Params.Bool("restart_network"); !restart {
self.SetStageComplete(ctx, nil)
return
}
prevIp, err := self.Params.GetString("prev_ip")
if err != nil {
log.Errorf("unable to get prev_ip when restart_network is true when sync guest")
self.SetStageComplete(ctx, nil)
return
}
if inBlockStream := jsonutils.QueryBoolean(self.Params, "in_block_stream", false); inBlockStream {
guest.StartRestartNetworkTask(ctx, self.UserCred, "", prevIp, true)
} else {
guest.StartRestartNetworkTask(ctx, self.UserCred, "", prevIp, false)
}
self.SetStageComplete(ctx, guest.GetShortDesc(ctx))
if restart, _ := self.Params.Bool("restart_network"); restart {
self.StartRestartNetworkTask(ctx, guest)
} else if data.Contains("task") {
// XXX this is only applied to KVM, which will call task_complete twice
self.SetStage("on_disk_sync_complete", nil)
@@ -78,6 +67,67 @@ func (self *GuestSyncConfTask) OnSyncComplete(ctx context.Context, obj db.IStand
}
}
func (self *GuestSyncConfTask) StartRestartNetworkTask(ctx context.Context, guest *models.SGuest) {
defer self.SetStageComplete(ctx, guest.GetShortDesc(ctx))
prevIp, err := self.Params.GetString("prev_ip")
if err != nil {
log.Errorf("unable to get prev_ip when restart_network is true when sync guest")
return
}
inBlockStream := jsonutils.QueryBoolean(self.Params, "in_block_stream", false)
if guest.Hypervisor != api.HYPERVISOR_KVM {
guest.StartRestartNetworkTask(ctx, self.UserCred, "", prevIp, inBlockStream)
return
}
preMac, err := self.Params.GetString("prev_mac")
if err != nil {
log.Errorf("unable to get prev_mac when restart_network is true when sync guest")
return
}
ipMask, err := self.Params.GetString("ip_mask")
if err != nil {
log.Errorf("unable to get ip_mask when restart_network is true when sync guest")
return
}
gateway, err := self.Params.GetString("gateway")
if err != nil {
log.Errorf("unable to get gateway when restart_network is true when sync guest")
return
}
isVpcNetwork := jsonutils.QueryBoolean(self.Params, "is_vpc_network", false)
// try use qga restart network
err = func() error {
host, _ := guest.GetHost()
err = guest.GetDriver().QgaRequestGuestPing(ctx, self.GetTaskRequestHeader(), host, guest, false, &api.ServerQgaTimeoutInput{1000})
if err != nil {
return errors.Wrap(err, "qga guest-ping")
}
ifnameDevice, err := guest.GetIfNameByMac(ctx, self.UserCred, preMac)
if err != nil {
return errors.Wrap(err, "get ifname by mac")
}
if ifnameDevice == "" {
return errors.Errorf("failed find ifname")
}
if isVpcNetwork {
err = vpcagent.VpcAgent.DoSync(auth.GetAdminSession(ctx, options.Options.Region))
if err != nil {
log.Errorf("vpcagent.VpcAgent.DoSync fail %s", err)
}
// wait for vpcagent sync network topo
time.Sleep(10 * time.Second)
}
return guest.StartQgaRestartNetworkTask(
ctx, self.UserCred, "", ifnameDevice, ipMask, gateway, prevIp, inBlockStream)
}()
if err != nil {
log.Errorf("guest %s failed start qga restart network task: %s", guest.GetName(), err)
guest.StartRestartNetworkTask(ctx, self.UserCred, "", prevIp, inBlockStream)
}
}
func (self *GuestSyncConfTask) OnDiskSyncComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
if jsonutils.QueryBoolean(self.Params, "without_sync_status", false) {
self.OnSyncStatusComplete(ctx, guest, nil)

View File

@@ -129,6 +129,9 @@ func DoDeployGuestFs(rootfs fsdriver.IRootFsDriver, guestDesc *deployapi.GuestDe
if err = rootfs.DeployHosts(partition, hn, domain, ips); err != nil {
return nil, errors.Wrap(err, "DeployHosts")
}
if err = rootfs.DeployQgaBlackList(partition); err != nil {
return nil, fmt.Errorf("DeployQgaBlackList: %v", err)
}
if err = rootfs.DeployNetworkingScripts(partition, nics); err != nil {
return nil, errors.Wrap(err, "DeployNetworkingScripts")
}

View File

@@ -90,6 +90,10 @@ 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"
}

View File

@@ -64,6 +64,10 @@ 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
}

View File

@@ -75,6 +75,7 @@ type IRootFsDriver interface {
GetOs() string
DeployHostname(part IDiskPartition, hn, domain string) error
DeployHosts(part IDiskPartition, hn, domain string, ips []string) error
DeployQgaBlackList(part IDiskPartition) error
DeployNetworkingScripts(IDiskPartition, []*types.SServerNic) error
DeployStandbyNetworkingScripts(part IDiskPartition, nics, nicsStandby []*types.SServerNic) error
DeployUdevSubsystemScripts(IDiskPartition) error

View File

@@ -80,6 +80,35 @@ func getHostname(hostname, domain string) string {
}
}
func (l *sLinuxRootFs) DeployQgaBlackList(rootFs IDiskPartition) error {
etcSysconfigQemuga := "/etc/sysconfig/qemu-ga"
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 err := rootFs.FilePutContents(etcSysconfigQemuga, blackListContent, false, false); err != nil {
return errors.Wrap(err, "etcSysconfigQemuga error")
}
return nil
}
func (l *sLinuxRootFs) DeployHosts(rootFs IDiskPartition, hostname, domain string, ips []string) error {
var etcHosts = "/etc/hosts"
var oldHostFile string
@@ -160,6 +189,37 @@ func (l *sLinuxRootFs) DeployPublicKey(rootFs IDiskPartition, selUsr string, pub
return DeployAuthorizedKeys(rootFs, usrDir, pubkeys, false)
}
func (d *SCoreOsRootFs) DeployQgaBlackList(rootFs IDiskPartition) error {
etcSysconfigQemuga := "/etc/sysconfig/qemu-ga"
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)

View File

@@ -116,6 +116,10 @@ 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)

View File

@@ -277,6 +277,10 @@ 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 {

View File

@@ -104,3 +104,59 @@ func (m *SGuestManager) QgaCommand(cmd *monitor.Command, sid string, execTimeout
return string(res), err
}
func (m *SGuestManager) QgaGuestInfoTask(sid string) (string, error) {
guest, err := m.checkAndInitGuestQga(sid)
if err != nil {
return "", err
}
var res []byte
if guest.guestAgent.TryLock() {
defer guest.guestAgent.Unlock()
res, err = guest.guestAgent.GuestInfoTask()
if err != nil {
return "", errors.Wrap(err, "qga guest info task")
}
return string(res), nil
}
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)
if err != nil {
return "", err
}
var res []byte
if guest.guestAgent.TryLock() {
defer guest.guestAgent.Unlock()
if execTimeout > 0 {
guest.guestAgent.SetTimeout(execTimeout)
defer guest.guestAgent.ResetTimeout()
}
err = guest.guestAgent.QgaSetNetwork(netmod)
if err != nil {
return "", errors.Wrapf(err, "modify %s network failed", netmod.Device)
}
return string(res), nil
}
return "", errors.Errorf("qga unfinished last cmd, is qga unavailable?")
}
func (m *SGuestManager) QgaGetNetwork(sid string) (string, error) {
guest, err := m.checkAndInitGuestQga(sid)
if err != nil {
return "", err
}
var res []byte
if guest.guestAgent.TryLock() {
defer guest.guestAgent.Unlock()
res, err = guest.guestAgent.QgaGetNetwork()
if err != nil {
return "", errors.Wrap(err, "qga get network fail")
}
return string(res), nil
}
return "", errors.Errorf("qga unfinished last cmd, is qga unavailable?")
}

View File

@@ -99,6 +99,9 @@ func AddGuestTaskHandler(prefix string, app *appsrv.Application) {
"qga-command": qgaCommand,
"reset-nic-traffic-limit": guestResetNicTrafficLimit,
"set-nic-traffic-limit": guestSetNicTrafficLimit,
"qga-guest-info-task": qgaGuestInfoTask,
"qga-get-network": qgaGetNetwork,
"qga-set-network": qgaSetNetwork,
} {
app.AddHandler("POST",
fmt.Sprintf("%s/%s/<sid>/%s", prefix, keyWord, action),
@@ -878,3 +881,38 @@ func qgaCommand(ctx context.Context, userCred mcclient.TokenCredential, sid stri
return gm.QgaCommand(qgaCmd, sid, input.Timeout)
}
func qgaGuestInfoTask(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
gm := guestman.GetGuestManager()
return gm.QgaGuestInfoTask(sid)
}
func qgaGetNetwork(ctx context.Context, userCred mcclient.TokenCredential, sid string, body jsonutils.JSONObject) (interface{}, error) {
gm := guestman.GetGuestManager()
return gm.QgaGetNetwork(sid)
}
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 {
return nil, httperrors.NewInputParameterError("unmarshal input to ServerQgaSetNetworkInput: %s", err.Error())
}
if input.Device == "" {
return nil, httperrors.NewMissingParameterError("device")
}
if input.Ipmask == "" {
return nil, httperrors.NewMissingParameterError("ipmask")
}
if input.Gateway == "" {
return nil, httperrors.NewMissingParameterError("gateway")
}
qgaNetMod := &monitor.NetworkModify{
Device: input.Device,
Ipmask: input.Ipmask,
Gateway: input.Gateway,
}
return gm.QgaSetNetwork(qgaNetMod, sid, input.Timeout)
}

View File

@@ -2025,6 +2025,7 @@ func getNicBridge(nic *desc.SGuestNetwork) string {
}
func onNicChange(oldNic, newNic *desc.SGuestNetwork) error {
log.Infof("nic changed old: %s new: %s", jsonutils.Marshal(oldNic), jsonutils.Marshal(newNic))
// override network base desc
oldNic.GuestnetworkBaseDesc = newNic.GuestnetworkBaseDesc

View File

@@ -18,7 +18,10 @@ import (
"bufio"
"encoding/base64"
"encoding/json"
"fmt"
"net"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
@@ -173,7 +176,7 @@ func (qga *QemuGuestAgent) execCmd(cmd *monitor.Command, expectResp bool, readTi
return nil, nil
}
if readTimeout < 0 {
if readTimeout <= 0 {
readTimeout = qga.readTimeout
}
err = qga.rwc.SetReadDeadline(time.Now().Add(time.Duration(readTimeout) * time.Millisecond))
@@ -245,6 +248,217 @@ func (qga *QemuGuestAgent) GuestInfo() (*GuestInfo, error) {
return res, nil
}
func (qga *QemuGuestAgent) GuestInfoTask() ([]byte, error) {
info, err := qga.GuestInfo()
if err != nil {
return nil, err
}
cmd := &monitor.Command{
Execute: "guest-info",
}
var i = 0
for ; i < len(info.SupportedCommands); i++ {
if info.SupportedCommands[i].Name == cmd.Execute {
break
}
}
if i > len(info.SupportedCommands) {
return nil, errors.Errorf("unsupported command %s", cmd.Execute)
}
if !info.SupportedCommands[i].Enabled {
return nil, errors.Errorf("command %s not enabled", cmd.Execute)
}
res, err := qga.execCmd(cmd, true, -1)
if err != nil {
return nil, err
}
return *res, nil
}
func (qga *QemuGuestAgent) QgaGetNetwork() ([]byte, error) {
cmd := &monitor.Command{
Execute: "guest-network-get-interfaces",
}
res, err := qga.execCmd(cmd, true, -1)
if err != nil {
return nil, err
}
return *res, nil
}
type GuestOsInfo struct {
Id string `json:"id"`
KernelRelease string `json:"kernel-release"`
KernelVersion string `json:"kernel-version"`
Machine string `json:"machine"`
Name string `json:"name"`
PrettyName string `json:"pretty-name"`
Version string `json:"version"`
VersionId string `json:"version-id"`
}
func (qga *QemuGuestAgent) QgaGuestGetOsInfo() (*GuestOsInfo, error) {
//run guest-get-osinfo
cmdOsInfo := &monitor.Command{
Execute: "guest-get-osinfo",
}
rawResOsInfo, err := qga.execCmd(cmdOsInfo, true, -1)
resOsInfo := new(GuestOsInfo)
err = json.Unmarshal(*rawResOsInfo, resOsInfo)
if err != nil {
return nil, errors.Wrap(err, "unmarshal raw response")
}
return resOsInfo, nil
}
func (qga *QemuGuestAgent) QgaFileOpen(path string) (int, error) {
//file open
cmdFileOpen := &monitor.Command{
Execute: "guest-file-open",
Args: map[string]interface{}{
"path": path,
"mode": "w+",
},
}
rawResFileOpen, err := qga.execCmd(cmdFileOpen, true, -1)
if err != nil {
return 0, err
}
fileNum, err := strconv.ParseInt(string(*rawResFileOpen), 10, 64)
if err != nil {
return 0, err
}
return int(fileNum), nil
}
func (qga *QemuGuestAgent) QgaFileWrite(fileNum int, content string) error {
contentEncode := base64.StdEncoding.EncodeToString([]byte(content))
//write shell to file
cmdFileWrite := &monitor.Command{
Execute: "guest-file-write",
Args: map[string]interface{}{
"handle": fileNum,
"buf-b64": contentEncode,
},
}
_, err := qga.execCmd(cmdFileWrite, true, -1)
if err != nil {
return err
}
return nil
}
func (qga *QemuGuestAgent) QgaFileClose(fileNum int) error {
//close file
cmdFileClose := &monitor.Command{
Execute: "guest-file-close",
Args: map[string]interface{}{
"handle": fileNum,
},
}
_, err := qga.execCmd(cmdFileClose, true, -1)
if err != nil {
return err
}
return nil
}
func ParseIPAndSubnet(input string) (string, string, error) {
//Converting IP/MASK format to IP and MASK
parts := strings.Split(input, "/")
if len(parts) != 2 {
return "", "", fmt.Errorf("Invalid input format")
}
ip := parts[0]
subnetSizeStr := parts[1]
subnetSize := 0
for _, c := range subnetSizeStr {
if c < '0' || c > '9' {
return "", "", fmt.Errorf("Invalid subnet size")
}
subnetSize = subnetSize*10 + int(c-'0')
}
mask := net.CIDRMask(subnetSize, 32)
subnetMask := net.IP(mask).To4().String()
return ip, subnetMask, nil
}
func (qga *QemuGuestAgent) QgaAddFileExec(filePath string) error {
//Adding execution permissions to file
shellAddAuth := "chmod +x " + filePath
arg := []string{"-c", shellAddAuth}
cmdAddAuth := &monitor.Command{
Execute: "guest-exec",
Args: map[string]interface{}{
"path": "/bin/bash",
"arg": arg,
"env": []string{},
"input-data": "",
"capture-output": true,
},
}
_, err := qga.execCmd(cmdAddAuth, true, -1)
if err != nil {
return err
}
return nil
}
func (qga *QemuGuestAgent) QgaSetWindowsNetwork(qgaNetMod *monitor.NetworkModify) error {
ip, subnetMask, err := ParseIPAndSubnet(qgaNetMod.Ipmask)
if err != nil {
return err
}
networkCmd := fmt.Sprintf(
"netsh interface ip set address name=\"%s\" source=static addr=%s mask=%s gateway=%s & "+
"netsh interface ip set address name=\"%s\" dhcp",
qgaNetMod.Device, ip, subnetMask, qgaNetMod.Gateway, qgaNetMod.Device,
)
log.Infof("networkCmd: %s", networkCmd)
arg := []string{"/C", networkCmd}
cmdExecNet := &monitor.Command{
Execute: "guest-exec",
Args: map[string]interface{}{
"path": "C:\\Windows\\System32\\cmd.exe",
"arg": arg,
"env": []string{},
"input-data": "",
"capture-output": true,
},
}
_, err = qga.execCmd(cmdExecNet, true, -1)
if err != nil {
return err
}
return nil
}
func (qga *QemuGuestAgent) QgaSetLinuxNetwork(qgaNetMod *monitor.NetworkModify) error {
args := []string{"-c", fmt.Sprintf("/sbin/dhclient -r %s && /sbin/dhclient -1 %s", qgaNetMod.Device, qgaNetMod.Device)}
_, err := qga.GuestExecCommand("/bin/bash", args, []string{}, "", false)
return err
}
func (qga *QemuGuestAgent) QgaSetNetwork(qgaNetMod *monitor.NetworkModify) error {
//Getting information about the operating system
resOsInfo, err := qga.QgaGuestGetOsInfo()
if err != nil {
return errors.Wrap(err, "get os info")
}
//Judgement based on id, currently only windows and other systems are judged
switch resOsInfo.Id {
case "mswindows":
return qga.QgaSetWindowsNetwork(qgaNetMod)
default:
return qga.QgaSetLinuxNetwork(qgaNetMod)
}
}
/*
# @username: the user account whose password to change
# @password: the new password entry string, base64 encoded

View File

@@ -82,6 +82,12 @@ type Command struct {
Args interface{} `json:"arguments,omitempty"`
}
type NetworkModify struct {
Device string `json:"device"`
Ipmask string `json:"ipmask"`
Gateway string `json:"gateway"`
}
type Version struct {
Package string `json:"package"`
QEMU struct {

View File

@@ -857,6 +857,22 @@ func (o *ServerQgaPing) Params() (jsonutils.JSONObject, error) {
return options.StructToParams(o)
}
type ServerQgaGuestInfoTask struct {
ServerIdOptions
}
func (o *ServerQgaGuestInfoTask) Params() (jsonutils.JSONObject, error) {
return options.StructToParams(o)
}
type ServerQgaGetNetwork struct {
ServerIdOptions
}
func (o *ServerQgaGetNetwork) Params() (jsonutils.JSONObject, error) {
return options.StructToParams(o)
}
type ServerSetPasswordOptions struct {
ServerIdOptions

View File

@@ -227,6 +227,10 @@ const (
ACT_RESTART_NETWORK = "restart_network"
ACT_QGA_NETWORK_INPUT = "qga_network_input"
ACT_QGA_STATUS_UPDATE = "qga_status_update"
ACT_QGA_NETWORK_SUCCESS = "qga_network_success"
ACT_RECOVERY = "recovery"
ACT_PACK = "pack"
ACT_UNPACK = "unpack"