Files
cloudpods/pkg/compute/models/guest_actions.go
2026-09-08 15:50:11 +08:00

7643 lines
261 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 models
import (
"context"
"database/sql"
"fmt"
"net"
"net/http"
"strconv"
"strings"
"time"
"unicode"
"yunion.io/x/cloudmux/pkg/cloudprovider"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/gotypes"
"yunion.io/x/pkg/tristate"
"yunion.io/x/pkg/util/billing"
"yunion.io/x/pkg/util/httputils"
"yunion.io/x/pkg/util/osprofile"
"yunion.io/x/pkg/util/rand"
"yunion.io/x/pkg/util/regutils"
"yunion.io/x/pkg/util/sets"
"yunion.io/x/pkg/utils"
"yunion.io/x/sqlchemy"
"yunion.io/x/onecloud/pkg/apis"
billing_api "yunion.io/x/onecloud/pkg/apis/billing"
api "yunion.io/x/onecloud/pkg/apis/compute"
imageapi "yunion.io/x/onecloud/pkg/apis/image"
noapi "yunion.io/x/onecloud/pkg/apis/notify"
schedapi "yunion.io/x/onecloud/pkg/apis/scheduler"
"yunion.io/x/onecloud/pkg/cloudcommon/cmdline"
"yunion.io/x/onecloud/pkg/cloudcommon/consts"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"yunion.io/x/onecloud/pkg/cloudcommon/db/quotas"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
"yunion.io/x/onecloud/pkg/cloudcommon/policy"
"yunion.io/x/onecloud/pkg/cloudcommon/userdata"
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
"yunion.io/x/onecloud/pkg/compute/baremetal"
guestdriver_types "yunion.io/x/onecloud/pkg/compute/guestdrivers/types"
"yunion.io/x/onecloud/pkg/compute/options"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcclient/modules/image"
"yunion.io/x/onecloud/pkg/mcclient/modules/notify"
"yunion.io/x/onecloud/pkg/mcclient/modules/scheduler"
"yunion.io/x/onecloud/pkg/util/bitmap"
"yunion.io/x/onecloud/pkg/util/logclient"
"yunion.io/x/onecloud/pkg/util/netutils2"
"yunion.io/x/onecloud/pkg/util/seclib2"
)
// 获取VNC信息
func (self *SGuest) GetDetailsVnc(ctx context.Context, userCred mcclient.TokenCredential, input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
ret := &cloudprovider.ServerVncOutput{}
if self.PowerStates == api.VM_POWER_STATES_ON ||
utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_BLOCK_STREAM, api.VM_MIGRATING}) {
host, err := self.GetHost()
if err != nil {
return nil, httperrors.NewInternalServerError("get host %v", err)
}
if options.Options.ForceUseOriginVnc {
input.Origin = true
}
driver, err := self.GetDriver()
if err != nil {
return nil, errors.Wrapf(err, "GetDriver")
}
ret, err = driver.GetGuestVncInfo(ctx, userCred, self, host, input)
if err != nil {
return nil, err
}
ret.Id = self.Id
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_CONSOLE, ret, userCred, true)
return ret, nil
}
return ret, nil
}
// 获取公有云可变更配置
// 目前支持: 阿里云,腾讯云,华为云
func (guest *SGuest) GetDetailsModificationTypes(ctx context.Context, userCred mcclient.TokenCredential, input jsonutils.JSONObject) (*api.ServerModificationTypesOutput, error) {
if len(guest.ExternalId) == 0 {
return nil, httperrors.NewNotFoundError("guest external id is empty")
}
vm, err := guest.GetIVM(ctx)
if err != nil {
return nil, errors.Wrapf(err, "GetIVM")
}
ret := &api.ServerModificationTypesOutput{}
ret.ModificationTypes = make([]api.ServerModificationType, 0)
modificationTypes, err := vm.GetModificationTypes()
if err != nil {
return nil, errors.Wrapf(err, "GetModificationTypes")
}
for _, modificationType := range modificationTypes {
ret.ModificationTypes = append(ret.ModificationTypes, api.ServerModificationType{Name: modificationType.InstanceType})
}
return ret, nil
}
func (self *SGuest) PreCheckPerformAction(
ctx context.Context, userCred mcclient.TokenCredential,
action string, query jsonutils.JSONObject, data jsonutils.JSONObject,
) error {
if err := self.SVirtualResourceBase.PreCheckPerformAction(ctx, userCred, action, query, data); err != nil {
return err
}
if self.Hypervisor == api.HYPERVISOR_KVM {
host, _ := self.GetHost()
if host != nil && (host.HostStatus == api.HOST_OFFLINE || !host.Enabled.Bool()) &&
utils.IsInStringArray(action,
[]string{
"start", "restart", "stop", "reset", "rebuild-root",
"change-config", "instance-snapshot", "snapshot-and-clone",
"attach-isolated-device", "detach-isolated-deivce",
"insert-iso", "eject-iso", "deploy", "create-backup",
}) {
return httperrors.NewInvalidStatusError(
"host status %s and enabled %v, can't do server %s", host.HostStatus, host.Enabled.Bool(), action)
}
}
return nil
}
// 远程执行QMP命令
func (self *SGuest) PerformMonitor(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input *api.ServerMonitorInput,
) (jsonutils.JSONObject, error) {
if self.PowerStates == api.VM_POWER_STATES_ON ||
utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_BLOCK_STREAM, api.VM_MIGRATING}) {
if input.COMMAND == "" {
return nil, httperrors.NewMissingParameterError("command")
}
return self.SendMonitorCommand(ctx, userCred, input)
}
return nil, httperrors.NewInvalidStatusError("Cannot send command in status %s", self.Status)
}
// +onecloud:swagger-gen-ignore
func (self *SGuest) PerformEvent(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject,
data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
event, err := data.GetString("event")
if err != nil {
return nil, httperrors.NewMissingParameterError("event")
}
if event == "GUEST_PANICKED" {
kwargs := jsonutils.NewDict()
kwargs.Set("reason", jsonutils.NewString(event))
if data.Contains("screen_dump_info") {
screenDumpInfo := api.SGuestScreenDumpInfo{}
if err := data.Unmarshal(&screenDumpInfo, "screen_dump_info"); err != nil {
log.Errorf("failed unmarshal screen_dump_info %s", err)
} else {
kwargs.Set("screen_dump_name", jsonutils.NewString(screenDumpInfo.S3ObjectName))
if _, err := self.SaveGuestScreenDump(ctx, userCred, &screenDumpInfo); err != nil {
log.Errorf("SaveGuestScreenDump failed %s", err)
}
}
}
db.OpsLog.LogEvent(self, db.ACT_GUEST_PANICKED, kwargs.String(), userCred)
logclient.AddSimpleActionLog(self, logclient.ACT_GUEST_PANICKED, kwargs.String(), userCred, true)
notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{
Obj: self,
Action: notifyclient.ActionServerPanicked,
IsFail: true,
})
}
return nil, nil
}
func (self *SGuest) GetDetailsDesc(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
host, err := self.GetHost()
if err != nil {
return nil, errors.Wrapf(err, "GetHost")
}
driver, err := self.GetDriver()
if err != nil {
return nil, err
}
return driver.GetJsonDescAtHost(ctx, userCred, self, host, nil)
}
// 保存镜像
func (self *SGuest) PerformSaveImage(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerSaveImageInput) (api.ServerSaveImageInput, error) {
if !utils.IsInStringArray(self.Status, []string{api.VM_READY, api.VM_RUNNING}) {
return input, httperrors.NewInputParameterError("Cannot save image in status %s", self.Status)
}
driver, err := self.GetDriver()
if err != nil {
return input, errors.Wrapf(err, "GetDriver")
}
input.Restart = ((self.Status == api.VM_RUNNING) || input.AutoStart) && !driver.IsAllowSaveImageOnRunning()
if len(input.Name) == 0 && len(input.GenerateName) == 0 {
return input, httperrors.NewInputParameterError("Image name is required")
}
disks := self.CategorizeDisks()
if disks.Root == nil {
return input, httperrors.NewInputParameterError("No root image")
}
input.OsType = self.OsType
if len(input.OsType) == 0 {
input.OsType = "Linux"
}
input.OsArch = self.OsArch
if apis.IsARM(self.OsArch) || apis.IsRISCV(self.OsArch) {
if osArch := self.GetMetadata(ctx, "os_arch", nil); len(osArch) == 0 {
host, _ := self.GetHost()
input.OsArch = host.CpuArchitecture
}
}
factory, _ := cloudprovider.GetProviderFactory(driver.GetProvider())
if factory == nil || factory.IsOnPremise() { // OneCloud or VMware
lockman.LockObject(ctx, disks.Root)
defer lockman.ReleaseObject(ctx, disks.Root)
var err error
input.ImageId, err = disks.Root.PrepareSaveImage(ctx, userCred, input)
if err != nil {
return input, errors.Wrapf(err, "PrepareSaveImage")
}
}
if len(input.Name) == 0 {
input.Name = input.GenerateName
}
logclient.AddSimpleActionLog(self, logclient.ACT_SAVE_IMAGE, input, userCred, true)
return input, self.StartGuestSaveImage(ctx, userCred, input, "")
}
func (self *SGuest) StartGuestSaveImage(ctx context.Context, userCred mcclient.TokenCredential, input api.ServerSaveImageInput, parentTaskId string) error {
driver, err := self.GetDriver()
if err != nil {
return errors.Wrapf(err, "GetDriver")
}
return driver.StartGuestSaveImage(ctx, userCred, self, jsonutils.Marshal(input).(*jsonutils.JSONDict), parentTaskId)
}
// 保存主机模板
func (self *SGuest) PerformSaveGuestImage(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, input api.ServerSaveGuestImageInput) (jsonutils.JSONObject, error) {
if !utils.IsInStringArray(self.Status, []string{api.VM_READY}) {
return nil, httperrors.NewBadRequestError("Cannot save image in status %s", self.Status)
}
if len(input.Name) == 0 && len(input.GenerateName) == 0 {
return nil, httperrors.NewMissingParameterError("Image name is required")
}
if self.Hypervisor != api.HYPERVISOR_KVM {
return nil, httperrors.NewBadRequestError("Support only by KVM Hypervisor")
}
disks := self.CategorizeDisks()
if disks.Root == nil {
return nil, httperrors.NewInternalServerError("No root image")
}
if len(self.EncryptKeyId) > 0 && (input.EncryptKeyId == nil || len(*input.EncryptKeyId) == 0) {
// server encrypted, so image must be encrypted
input.EncryptKeyId = &self.EncryptKeyId
} else if len(self.EncryptKeyId) > 0 && input.EncryptKeyId != nil && len(*input.EncryptKeyId) > 0 && self.EncryptKeyId != *input.EncryptKeyId {
return nil, errors.Wrap(httperrors.ErrConflict, "input encrypt key not match with server encrypt key")
}
diskList := append(disks.Data, disks.Root)
kwargs := imageapi.GuestImageCreateInput{}
kwargs.GuestImageCreateInputBase = input.GuestImageCreateInputBase
kwargs.Properties = make(map[string]string)
if len(kwargs.ProjectId) == 0 {
kwargs.ProjectId = self.ProjectId
}
for _, disk := range diskList {
kwargs.Images = append(kwargs.Images, imageapi.GuestImageCreateInputSubimage{
DiskFormat: disk.DiskFormat,
VirtualSize: disk.DiskSize,
})
}
if len(input.Notes) > 0 {
kwargs.Properties["notes"] = input.Notes
}
osType := self.OsType
if len(osType) == 0 {
osType = "Linux"
}
kwargs.Properties["os_type"] = osType
if apis.IsARM(self.OsArch) || apis.IsRISCV(self.OsArch) {
var osArch string
if osArch = self.GetMetadata(ctx, "os_arch", nil); len(osArch) == 0 {
host, _ := self.GetHost()
osArch = host.CpuArchitecture
}
kwargs.Properties["os_arch"] = osArch
kwargs.OsArch = self.OsArch
}
s := auth.GetSession(ctx, userCred, consts.GetRegion())
ret, err := image.GuestImages.Create(s, jsonutils.Marshal(kwargs))
if err != nil {
return nil, err
}
guestImageId, _ := ret.GetString("id")
// set class metadata
cm, err := self.GetAllClassMetadata()
if err != nil {
return nil, errors.Wrap(err, "unable to GetAllClassMetadata")
}
if len(cm) > 0 {
_, err = image.GuestImages.PerformAction(s, guestImageId, "set-class-metadata", jsonutils.Marshal(cm))
if err != nil {
return nil, errors.Wrapf(err, "unable to SetClassMetadata for guest image %s", guestImageId)
}
}
guestImageInfo := struct {
RootImage imageapi.SubImageInfo
DataImages []imageapi.SubImageInfo
}{}
ret.Unmarshal(&guestImageInfo)
if len(guestImageInfo.DataImages) != len(disks.Data) {
return nil, fmt.Errorf("create subimage of guest image error")
}
imageIds := make([]string, 0, len(guestImageInfo.DataImages)+1)
for _, info := range guestImageInfo.DataImages {
imageIds = append(imageIds, info.ID)
}
imageIds = append(imageIds, guestImageInfo.RootImage.ID)
taskParams := jsonutils.NewDict()
if input.AutoStart != nil && *input.AutoStart {
taskParams.Add(jsonutils.JSONTrue, "auto_start")
}
taskParams.Add(jsonutils.Marshal(imageIds), "image_ids")
log.Infof("before StartGuestSaveGuestImage image_ids: %s", imageIds)
return nil, self.StartGuestSaveGuestImage(ctx, userCred, taskParams, "")
}
func (self *SGuest) StartGuestSaveGuestImage(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict, parentTaskId string) error {
driver, err := self.GetDriver()
if err != nil {
return err
}
return driver.StartGuestSaveGuestImage(ctx, userCred, self, data, parentTaskId)
}
// 同步配置
func (self *SGuest) PerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if !utils.IsInStringArray(self.Status, []string{api.VM_READY, api.VM_RUNNING}) {
return nil, httperrors.NewResourceBusyError("Cannot sync in status %s", self.Status)
}
if err := self.StartSyncTask(ctx, userCred, false, ""); err != nil {
return nil, err
}
return nil, nil
}
func (self *SGuest) GetQemuVersion(userCred mcclient.TokenCredential) string {
return self.GetMetadata(context.Background(), "__qemu_version", userCred)
}
func (self *SGuest) GetQemuCmdline(userCred mcclient.TokenCredential) string {
return self.GetMetadata(context.Background(), "__qemu_cmdline", userCred)
}
func (self *SGuest) GetDetailsQemuInfo(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*api.ServerQemuInfo, error) {
version := self.GetQemuVersion(userCred)
cmdline := self.GetQemuCmdline(userCred)
return &api.ServerQemuInfo{
Version: version,
Cmdline: cmdline,
}, nil
}
// if qemuVer >= compareVer return true
func (self *SGuest) CheckQemuVersion(qemuVer, compareVer string) bool {
if len(qemuVer) == 0 {
return false
}
compareVersion := strings.Split(compareVer, ".")
guestVersion := strings.Split(qemuVer, ".")
var i = 0
for ; i < len(guestVersion); i++ {
if i >= len(compareVersion) {
return true
}
v, _ := strconv.ParseInt(guestVersion[i], 10, 0)
compareV, _ := strconv.ParseInt(compareVersion[i], 10, 0)
if v < compareV {
return false
} else if v > compareV {
return true
}
}
if i < len(compareVersion)-1 {
return false
}
return true
}
func (self *SGuest) validateMigrate(
ctx context.Context,
userCred mcclient.TokenCredential,
migrateInput *api.GuestMigrateInput,
liveMigrateInput *api.GuestLiveMigrateInput,
) error {
isLiveMigrate := false
if liveMigrateInput != nil {
isLiveMigrate = true
}
driver, err := self.GetDriver()
if err != nil {
return errors.Wrapf(err, "GetDriver")
}
if isLiveMigrate {
// do live migrate check
if !driver.IsSupportLiveMigrate() {
return httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.GetHypervisor())
}
if err := driver.CheckLiveMigrate(ctx, self, userCred, *liveMigrateInput); err != nil {
return err
}
if utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_SUSPEND}) {
if len(liveMigrateInput.PreferHostId) > 0 {
iHost, _ := HostManager.FetchByIdOrName(ctx, userCred, liveMigrateInput.PreferHostId)
if iHost == nil {
return httperrors.NewBadRequestError("Host %s not found", liveMigrateInput.PreferHostId)
}
host := iHost.(*SHost)
liveMigrateInput.PreferHostId = host.Id
}
return nil
}
return httperrors.NewBadRequestError("Cannot live migrate in status %s", self.Status)
} else {
// do migrate check
if !driver.IsSupportMigrate() {
return httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.GetHypervisor())
}
if !migrateInput.IsRescueMode && self.Status != api.VM_READY {
return httperrors.NewServerStatusError("Cannot normal migrate guest in status %s, try rescue mode or server-live-migrate?", self.Status)
}
if err := driver.CheckMigrate(ctx, self, userCred, *migrateInput); err != nil {
return err
}
if len(migrateInput.PreferHostId) > 0 {
iHost, _ := HostManager.FetchByIdOrName(ctx, userCred, migrateInput.PreferHostId)
if iHost == nil {
return httperrors.NewBadRequestError("Host %s not found", migrateInput.PreferHostId)
}
host := iHost.(*SHost)
migrateInput.PreferHostId = host.Id
}
return nil
}
}
func (self *SGuest) validateConvertToKvm(
ctx context.Context,
userCred mcclient.TokenCredential,
migrateInput *api.GuestMigrateInput,
) error {
if len(migrateInput.PreferHostId) > 0 {
iHost, _ := HostManager.FetchByIdOrName(ctx, userCred, migrateInput.PreferHostId)
if iHost == nil {
return httperrors.NewBadRequestError("Host %s not found", migrateInput.PreferHostId)
}
host := iHost.(*SHost)
migrateInput.PreferHostId = host.Id
}
if self.Status != api.VM_READY {
return httperrors.NewServerStatusError("can't convert guest in status %s", self.Status)
}
return nil
}
// 迁移调度
func (self *SGuest) PerformMigrateForecast(ctx context.Context, userCred mcclient.TokenCredential, _ jsonutils.JSONObject, input *api.ServerMigrateForecastInput) (jsonutils.JSONObject, error) {
var (
mInput *api.GuestMigrateInput = nil
lmInput *api.GuestLiveMigrateInput = nil
)
if input.ConvertToKvm {
mInput = &api.GuestMigrateInput{PreferHostId: input.PreferHostId}
if err := self.validateConvertToKvm(ctx, userCred, mInput); err != nil {
return nil, err
}
input.PreferHostId = mInput.PreferHostId
} else {
if input.LiveMigrate {
lmInput = &api.GuestLiveMigrateInput{
PreferHostId: input.PreferHostId,
SkipCpuCheck: &input.SkipCpuCheck,
}
if err := self.validateMigrate(ctx, userCred, nil, lmInput); err != nil {
return nil, err
}
input.PreferHostId = lmInput.PreferHostId
} else {
mInput = &api.GuestMigrateInput{
PreferHostId: input.PreferHostId,
IsRescueMode: input.IsRescueMode,
}
if err := self.validateMigrate(ctx, userCred, mInput, nil); err != nil {
return nil, err
}
input.PreferHostId = mInput.PreferHostId
}
}
schedParams := self.GetSchedMigrateParams(ctx, userCred, input)
if input.ConvertToKvm {
schedParams.Hypervisor = api.HYPERVISOR_KVM
}
s := auth.GetAdminSession(ctx, options.Options.Region)
_, res, err := scheduler.SchedManager.DoScheduleForecast(s, schedParams, 1)
if err != nil {
return nil, errors.Wrap(err, "Do schedule migrate forecast")
}
return res, nil
}
func (self *SGuest) GetSchedMigrateParams(
ctx context.Context, userCred mcclient.TokenCredential,
input *api.ServerMigrateForecastInput,
) *schedapi.ScheduleInput {
schedDesc := self.ToSchedDesc()
if input.PreferHostId != "" {
schedDesc.ServerConfig.PreferHost = input.PreferHostId
} else {
createParams, err := self.GetCreateParams(ctx, userCred)
if err != nil {
log.Errorf("GetSchedMigrateParams failed get create params: %s", err)
} else {
schedDesc.Schedtags = createParams.Schedtags
}
}
schedDesc.ResetCpuNumaPin = input.ResetCpuNumaPin
if input.LiveMigrate {
schedDesc.LiveMigrate = input.LiveMigrate
if self.GetMetadata(context.Background(), "__cpu_mode", userCred) != api.CPU_MODE_QEMU {
host, _ := self.GetHost()
schedDesc.CpuDesc = host.CpuDesc
schedDesc.CpuMicrocode = host.CpuMicrocode
schedDesc.CpuMode = api.CPU_MODE_HOST
} else {
schedDesc.CpuMode = api.CPU_MODE_QEMU
}
schedDesc.SkipCpuCheck = &input.SkipCpuCheck
host, _ := self.GetHost()
if host != nil {
schedDesc.TargetHostKernel, _ = host.SysInfo.GetString("kernel_version")
schedDesc.SkipKernelCheck = &input.SkipKernelCheck
schedDesc.HostMemPageSizeKB = host.PageSizeKB
}
if self.IsSchedulerNumaAllocate() {
cpuNumaPin := make([]schedapi.SCpuNumaPin, 0)
self.CpuNumaPin.Unmarshal(&cpuNumaPin)
schedDesc.CpuNumaPin = cpuNumaPin
}
}
schedDesc.ReuseNetwork = true
return schedDesc
}
// 冷迁移虚拟机
func (self *SGuest) PerformMigrate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.GuestMigrateInput) (jsonutils.JSONObject, error) {
if err := self.validateMigrate(ctx, userCred, input, nil); err != nil {
return nil, err
}
return nil, self.StartMigrateTask(ctx, userCred, input.IsRescueMode, input.AutoStart, self.Status, input.PreferHostId, "")
}
func (self *SGuest) StartMigrateTask(
ctx context.Context, userCred mcclient.TokenCredential,
isRescueMode, autoStart bool, guestStatus, preferHostId, parentTaskId string,
) error {
vmStatus := api.VM_START_MIGRATE
data := jsonutils.NewDict()
if isRescueMode {
data.Set("is_rescue_mode", jsonutils.JSONTrue)
}
if len(preferHostId) > 0 {
data.Set("prefer_host_id", jsonutils.NewString(preferHostId))
}
if autoStart {
data.Set("auto_start", jsonutils.JSONTrue)
}
if self.HostId == preferHostId {
vmStatus = api.VM_STARTING
data.Set("reset_cpu_numa_pin", jsonutils.JSONTrue)
}
data.Set("guest_status", jsonutils.NewString(guestStatus))
dedicateMigrateTask := "GuestMigrateTask"
if len(self.ExternalId) > 0 {
dedicateMigrateTask = "ManagedGuestMigrateTask" //托管私有云
}
self.SetStatus(ctx, userCred, vmStatus, "")
if task, err := taskman.TaskManager.NewTask(ctx, dedicateMigrateTask, self, userCred, data, parentTaskId, "", nil); err != nil {
log.Errorln(err)
return err
} else {
task.ScheduleRun(nil)
}
return nil
}
// 在线迁移虚拟机
func (self *SGuest) PerformLiveMigrate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.GuestLiveMigrateInput) (jsonutils.JSONObject, error) {
if err := self.validateMigrate(ctx, userCred, nil, input); err != nil {
return nil, err
}
if input.EnableTLS == nil {
input.EnableTLS = &options.Options.EnableTlsMigration
}
return nil, self.StartGuestLiveMigrateTask(ctx, userCred,
self.Status, input.PreferHostId, input.SkipCpuCheck,
input.SkipKernelCheck, input.EnableTLS, input.QuicklyFinish, input.MaxBandwidthMb, input.KeepDestGuestOnFailed, "",
)
}
func (self *SGuest) StartGuestLiveMigrateTask(
ctx context.Context, userCred mcclient.TokenCredential,
guestStatus, preferHostId string,
skipCpuCheck, skipKernelCheck, enableTLS, quicklyFinish *bool,
maxBandwidthMb *int64, keepDestGuestOnFailed *bool, parentTaskId string,
) error {
self.SetStatus(ctx, userCred, api.VM_START_MIGRATE, "")
data := jsonutils.NewDict()
if len(preferHostId) > 0 {
data.Set("prefer_host_id", jsonutils.NewString(preferHostId))
}
if skipCpuCheck != nil {
data.Set("skip_cpu_check", jsonutils.NewBool(*skipCpuCheck))
}
if skipKernelCheck != nil {
data.Set("skip_kernel_check", jsonutils.NewBool(*skipKernelCheck))
}
if enableTLS != nil {
data.Set("enable_tls", jsonutils.NewBool(*enableTLS))
}
if quicklyFinish != nil {
data.Set("quickly_finish", jsonutils.NewBool(*quicklyFinish))
}
if maxBandwidthMb != nil {
data.Set("max_bandwidth_mb", jsonutils.NewInt(*maxBandwidthMb))
}
if keepDestGuestOnFailed != nil {
data.Set("keep_dest_guest_on_failed", jsonutils.NewBool(*keepDestGuestOnFailed))
}
data.Set("guest_status", jsonutils.NewString(guestStatus))
dedicateMigrateTask := "GuestLiveMigrateTask"
if len(self.ExternalId) > 0 {
dedicateMigrateTask = "ManagedGuestLiveMigrateTask" //托管私有云
}
if task, err := taskman.TaskManager.NewTask(ctx, dedicateMigrateTask, self, userCred, data, parentTaskId, "", nil); err != nil {
log.Errorln(err)
return err
} else {
task.ScheduleRun(nil)
}
return nil
}
// +onecloud:swagger-gen-ignore
func (self *SGuest) PerformSetLiveMigrateParams(
ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerSetLiveMigrateParamsInput,
) (jsonutils.JSONObject, error) {
if self.Status != api.VM_LIVE_MIGRATING {
return nil, httperrors.NewServerStatusError("cannot set migrate params in status %s", self.Status)
}
if input.MaxBandwidthMB == nil && input.DowntimeLimitMS == nil {
return nil, httperrors.NewInputParameterError("empty input")
}
arguments := map[string]interface{}{}
if input.MaxBandwidthMB != nil {
arguments["max-bandwidth"] = *input.MaxBandwidthMB * 1024 * 1024
}
if input.DowntimeLimitMS != nil {
arguments["downtime-limit"] = *input.DowntimeLimitMS
}
cmd := map[string]interface{}{
"execute": "migrate-set-parameters",
"arguments": arguments,
}
log.Infof("set live migrate params input: %s", jsonutils.Marshal(cmd).String())
monitorInput := &api.ServerMonitorInput{
COMMAND: jsonutils.Marshal(cmd).String(),
QMP: true,
}
return self.SendMonitorCommand(ctx, userCred, monitorInput)
}
// 取消在线迁移
func (self *SGuest) PerformCancelLiveMigrate(
ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
if self.Status != api.VM_LIVE_MIGRATING {
return nil, httperrors.NewServerStatusError("cannot set migrate params in status %s", self.Status)
}
driver, err := self.GetDriver()
if err != nil {
return nil, errors.Wrapf(err, "GetDriver")
}
return nil, driver.RequestCancelLiveMigrate(ctx, self, userCred)
}
// 克隆虚拟机
func (self *SGuest) PerformClone(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if self.IsEncrypted() {
return nil, httperrors.NewForbiddenError("cannot clone encrypted server")
}
if len(self.BackupHostId) > 0 {
return nil, httperrors.NewBadRequestError("Can't clone guest with backup guest")
}
driver, err := self.GetDriver()
if err != nil {
return nil, err
}
if !driver.IsSupportGuestClone() {
return nil, httperrors.NewBadRequestError("guest hypervisor %s does not support clone", self.Hypervisor)
}
if !utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_READY}) {
return nil, httperrors.NewInvalidStatusError("Cannot clone VM in status %s", self.Status)
}
cloneInput, err := cmdline.FetchServerCreateInputByJSON(data)
if err != nil {
return nil, httperrors.NewInputParameterError("unmarshal input failed %s", err)
}
if len(cloneInput.Name) == 0 {
return nil, httperrors.NewMissingParameterError("name")
}
err = db.NewNameValidator(ctx, GuestManager, userCred, cloneInput.Name, nil)
if err != nil {
return nil, err
}
createInput := self.ToCreateInput(ctx, userCred)
createInput.Name = cloneInput.Name
createInput.AutoStart = cloneInput.AutoStart
createInput.EipBw = cloneInput.EipBw
createInput.EipTxBw = cloneInput.EipTxBw
createInput.EipRxBw = cloneInput.EipRxBw
createInput.Eip = cloneInput.Eip
createInput.EipChargeType = cloneInput.EipChargeType
if err := GuestManager.validateEip(ctx, userCred, createInput, createInput.PreferRegion, createInput.PreferManager); err != nil {
return nil, err
}
dataDict := createInput.JSON(createInput)
// ownerId := db.SOwnerId{DomainId: createInput.Domain, ProjectId: createInput.Project}
model, err := db.DoCreate(GuestManager, ctx, userCred, query, dataDict, userCred)
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
func() {
lockman.LockObject(ctx, model)
defer lockman.ReleaseObject(ctx, model)
model.PostCreate(ctx, userCred, userCred, query, dataDict)
}()
db.OpsLog.LogEvent(model, db.ACT_CREATE, model.GetShortDesc(ctx), userCred)
logclient.AddActionLogWithContext(ctx, model, logclient.ACT_CREATE, "", userCred, true)
pendingUsage, pendingRegionUsage, err := getGuestResourceRequirements(ctx, userCred, *createInput, userCred, 1, false)
if err != nil {
return nil, errors.Wrap(err, "getGuestResourceRequirements")
}
task, err := taskman.TaskManager.NewTask(ctx,
"GuestCloneTask",
model.(db.IStandaloneModel),
userCred,
dataDict,
"", "",
&pendingUsage, &pendingRegionUsage)
if err != nil {
log.Errorln(err)
return nil, err
}
task.ScheduleRun(nil)
return nil, nil
}
// 重置密码
func (self *SGuest) PerformSetPassword(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerSetPasswordInput) (jsonutils.JSONObject, error) {
if self.Hypervisor == api.HYPERVISOR_KVM && self.Status == api.VM_RUNNING {
inputQga := &api.ServerQgaSetPasswordInput{
Username: input.Username,
Password: input.Password,
}
if inputQga.Username == "" {
if self.OsType == osprofile.OS_TYPE_WINDOWS {
inputQga.Username = api.VM_DEFAULT_WINDOWS_LOGIN_USER
} else {
inputQga.Username = api.VM_DEFAULT_LINUX_LOGIN_USER
}
}
if inputQga.Password == "" && input.ResetPassword {
inputQga.Password = seclib2.RandomPassword2(12)
}
return self.PerformQgaSetPassword(ctx, userCred, query, inputQga)
} else {
inputDeploy := api.ServerDeployInput{}
inputDeploy.AutoStart = input.AutoStart
inputDeploy.Password = input.Password
inputDeploy.ResetPassword = input.ResetPassword
if input.Username != "" {
inputDeploy.LoginAccount = input.Username
}
return self.PerformDeploy(ctx, userCred, query, inputDeploy)
}
}
func (self *SGuest) GetDetailsCreateParams(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
input := self.ToCreateInput(ctx, userCred)
return input.JSON(input), nil
}
func (self *SGuest) saveOldPassword(ctx context.Context, userCred mcclient.TokenCredential) {
loginKey := self.GetMetadata(ctx, api.VM_METADATA_LOGIN_KEY, userCred)
if len(loginKey) > 0 {
password, err := utils.DescryptAESBase64(self.Id, loginKey)
if err == nil && len(password) <= 30 {
for _, r := range password {
if !unicode.IsPrint(r) {
return
}
}
self.SetMetadata(ctx, api.VM_METADATA_LAST_LOGIN_KEY, loginKey, userCred)
}
}
}
func (self *SGuest) GetOldPassword(ctx context.Context, userCred mcclient.TokenCredential) string {
loginSecret := self.GetMetadata(ctx, api.VM_METADATA_LAST_LOGIN_KEY, userCred)
password, _ := utils.DescryptAESBase64(self.Id, loginSecret)
return password
}
// 重置密码或秘钥信息&探测虚拟机镜像
func (self *SGuest) PerformDeploy(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input api.ServerDeployInput,
) (jsonutils.JSONObject, error) {
self.saveOldPassword(ctx, userCred)
if input.DeleteKeypair || len(input.KeypairId) > 0 {
if len(input.KeypairId) > 0 {
_, err := validators.ValidateModel(ctx, userCred, KeypairManager, &input.KeypairId)
if err != nil {
return nil, err
}
}
if self.KeypairId != input.KeypairId {
okey := self.getKeypair()
if okey != nil {
input.DeletePublicKey = okey.PublicKey
}
diff, err := db.Update(self, func() error {
self.KeypairId = input.KeypairId
return nil
})
if err != nil {
return nil, httperrors.NewInternalServerError("update keypairId %v", err)
}
db.OpsLog.LogEvent(self, db.ACT_UPDATE, diff, userCred)
input.ResetPassword = true
}
}
if len(input.Password) > 0 {
err := seclib2.ValidatePassword(input.Password)
if err != nil {
return nil, err
}
input.ResetPassword = true
}
driver, err := self.GetDriver()
if err != nil {
return nil, errors.Wrapf(err, "GetDriver")
}
// 变更密码/密钥时需要Restart才能生效。更新普通字段不需要Restart, Azure需要在运行状态下操作
doRestart := false
if input.ResetPassword {
doRestart = driver.IsNeedRestartForResetLoginInfo()
if len(input.LoginAccount) > 0 {
if len(input.LoginAccount) > 32 {
return nil, httperrors.NewInputParameterError("login_account exceeds 32 characters")
}
if err := GuestManager.ValidateNameLoginAccount(input.LoginAccount); err != nil {
return nil, err
}
}
}
deployStatus, err := driver.GetDeployStatus()
if err != nil {
return nil, httperrors.NewInputParameterError("%v", err)
}
if utils.IsInStringArray(self.Status, deployStatus) {
if (doRestart && self.Status == api.VM_RUNNING) || (self.Status != api.VM_RUNNING && (input.AutoStart || input.Restart)) {
input.Restart = true
} else {
// 避免前端直接传restart参数, 越过校验
input.Restart = false
}
err := self.StartGuestDeployTask(ctx, userCred, jsonutils.Marshal(input).(*jsonutils.JSONDict), "deploy", "")
if err != nil {
return nil, err
}
return nil, nil
}
return nil, httperrors.NewServerStatusError("Cannot deploy in status %s", self.Status)
}
func (self *SGuest) ValidateAttachDisk(ctx context.Context, disk *SDisk) error {
storage, _ := disk.GetStorage()
host, _ := self.GetHost()
if provider := storage.GetCloudprovider(); provider != nil {
if provider.Id != host.ManagerId {
return httperrors.NewInputParameterError("Disk %s and guest not belong to the same account", disk.Name)
}
if storage.ZoneId != host.ZoneId {
return httperrors.NewInputParameterError("Disk %s and guest not belong to the same zone", disk.Name)
}
}
if disk.IsLocal() {
attached, err := disk.isAttached()
if err != nil {
return httperrors.NewInternalServerError("isAttached check failed %s", err)
}
if attached {
return httperrors.NewInputParameterError("Disk %s has been attached", disk.Name)
}
}
if len(disk.GetPathAtHost(host)) == 0 {
return httperrors.NewInputParameterError("Disk %s not belong the guest's host", disk.Name)
}
if disk.Status != api.DISK_READY {
return httperrors.NewInputParameterError("Disk in %s not able to attach", disk.Status)
}
driver, err := self.GetDriver()
if err != nil {
return errors.Wrapf(err, "GetDriver")
}
guestStatus, err := driver.GetAttachDiskStatus()
if err != nil {
return err
}
if !utils.IsInStringArray(self.Status, guestStatus) {
return httperrors.NewInputParameterError("guest %s does not support attaching disks in status %s", self.Name, self.Status)
}
ok, err := self.IsInSameClass(ctx, &disk.SStandaloneAnonResourceBase)
if err != nil {
return err
}
if self.EncryptKeyId != disk.EncryptKeyId {
return errors.Wrapf(httperrors.ErrConflict, "conflict encryption key between server and disk")
}
if !ok {
return httperrors.NewForbiddenError("the class metadata of guest and disk is different")
}
return nil
}
// 绑定磁盘
func (self *SGuest) PerformAttachdisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerAttachDiskInput) (jsonutils.JSONObject, error) {
if len(input.DiskId) == 0 {
return nil, httperrors.NewMissingParameterError("disk_id")
}
if input.BootIndex != nil {
if isDup, err := self.isBootIndexDuplicated(*input.BootIndex); err != nil {
return nil, err
} else if isDup {
return nil, httperrors.NewInputParameterError("boot index %d is duplicated", *input.BootIndex)
}
}
diskObj, err := validators.ValidateModel(ctx, userCred, DiskManager, &input.DiskId)
if err != nil {
return nil, err
}
if err := self.ValidateAttachDisk(ctx, diskObj.(*SDisk)); err != nil {
return nil, err
}
taskData := jsonutils.NewDict()
taskData.Add(jsonutils.NewString(input.DiskId), "disk_id")
if input.BootIndex != nil && *input.BootIndex >= 0 {
taskData.Add(jsonutils.NewInt(int64(*input.BootIndex)), "boot_index")
}
driver, err := self.GetDriver()
if err != nil {
return nil, errors.Wrapf(err, "GetDriver")
}
self.SetStatus(ctx, userCred, api.VM_ATTACH_DISK, "")
return nil, driver.StartGuestAttachDiskTask(ctx, userCred, self, taskData, "")
}
func (self *SGuest) StartRestartNetworkTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, ip string, inBlockStream bool) error {
data := jsonutils.NewDict()
data.Set("ip", jsonutils.NewString(ip))
data.Set("in_block_stream", jsonutils.NewBool(inBlockStream))
if task, err := taskman.TaskManager.NewTask(ctx, "GuestRestartNetworkTask", self, userCred, data, parentTaskId, "", nil); err != nil {
log.Errorln(err)
return err
} else {
task.ScheduleRun(nil)
}
return nil
}
func (self *SGuest) StartQgaRestartNetworkTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId, device, ipMask, gateway, ip6Mask, gateway6 string) error {
data := jsonutils.NewDict()
data.Set("device", jsonutils.NewString(device))
if len(ipMask) > 0 {
data.Set("ip_mask", jsonutils.NewString(ipMask))
data.Set("gateway", jsonutils.NewString(gateway))
}
if len(ip6Mask) > 0 {
data.Set("ip6_mask", jsonutils.NewString(ip6Mask))
data.Set("gateway6", jsonutils.NewString(gateway6))
}
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")
} else if err := self.SetStatus(ctx, userCred, api.VM_SYNC_CONFIG, ""); err != nil {
log.Errorln(err)
return err
}
return self.doSyncTask(ctx, data, userCred, parentTaskId)
}
func (self *SGuest) StartSyncTask(ctx context.Context, userCred mcclient.TokenCredential, firewallOnly bool, parentTaskId string) error {
return self.startSyncTask(ctx, userCred, firewallOnly, parentTaskId, jsonutils.NewDict())
}
func (self *SGuest) StartSyncTaskWithoutSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, fwOnly bool, parentTaskId string) error {
data := jsonutils.NewDict()
data.Set("without_sync_status", jsonutils.JSONTrue)
data.Set("fw_only", jsonutils.NewBool(fwOnly))
return self.doSyncTask(ctx, data, userCred, parentTaskId)
}
func (self *SGuest) doSyncTask(ctx context.Context, data *jsonutils.JSONDict, userCred mcclient.TokenCredential, parentTaskId string) error {
if task, err := taskman.TaskManager.NewTask(ctx, "GuestSyncConfTask", self, userCred, data, parentTaskId, "", nil); err != nil {
return err
} else {
task.ScheduleRun(nil)
}
return nil
}
// 挂起虚拟机
func (self *SGuest) PerformSuspend(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if self.Status == api.VM_RUNNING {
err := self.StartSuspendTask(ctx, userCred, "")
return nil, err
}
return nil, httperrors.NewInvalidStatusError("Cannot suspend VM in status %s", self.Status)
}
func (self *SGuest) StartSuspendTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
err := self.SetStatus(ctx, userCred, api.VM_START_SUSPEND, "do suspend")
if err != nil {
return err
}
driver, err := self.GetDriver()
if err != nil {
return errors.Wrapf(err, "GetDriver")
}
return driver.StartSuspendTask(ctx, userCred, self, nil, parentTaskId)
}
// 恢复虚拟机挂起状态
func (self *SGuest) PerformResume(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerResumeInput) (jsonutils.JSONObject, error) {
if self.Status == api.VM_SUSPEND {
err := self.StartResumeTask(ctx, userCred, "")
return nil, err
}
return nil, httperrors.NewInvalidStatusError("Cannot resume VM in status %s", self.Status)
}
func (self *SGuest) StartResumeTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
err := self.SetStatus(ctx, userCred, api.VM_RESUMING, "do resume")
if err != nil {
return err
}
driver, err := self.GetDriver()
if err != nil {
return errors.Wrapf(err, "GetDriver")
}
return driver.StartResumeTask(ctx, userCred, self, nil, parentTaskId)
}
func isolatedDeviceRestoreKey(devType, sharingMode, model string) string {
return devType + "\x00" + sharingMode + "\x00" + model
}
func parseIsolatedDeviceRestoreKey(key string) (devType, sharingMode, model string, ok bool) {
segs := strings.SplitN(key, "\x00", 3)
if len(segs) != 3 {
return "", "", "", false
}
return segs[0], segs[1], segs[2], true
}
func (self *SGuest) PerformRestoreVirtualIsolatedDevices(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
config := self.GetMetadataJson(ctx, api.VM_METADATA_VIRTUAL_ISOLATED_DEVICE_CONFIG, userCred)
if config == nil {
return nil, nil
}
devConfigs := make([]api.IsolatedDeviceConfig, 0)
err := config.Unmarshal(&devConfigs)
if err != nil {
return nil, errors.Wrap(err, "unmarshal virtual dev configs")
}
devs, err := self.GetGuestIsolatedDevices()
if err != nil {
return nil, errors.Wrap(err, "GetIsolatedDevices")
}
devCount := map[string]int{}
for i := range devConfigs {
key := isolatedDeviceRestoreKey(devConfigs[i].DevType, devConfigs[i].SharingMode, devConfigs[i].Model)
devCount[key] = devCount[key] + 1
}
for i := range devs {
isoDev := devs[i].GetIsolatedDevice()
key := isolatedDeviceRestoreKey(isoDev.DevType, isoDev.SharingMode, isoDev.Model)
if cnt, ok := devCount[key]; ok {
devCount[key] = cnt - 1
}
}
host, err := self.GetHost()
if err != nil {
return nil, errors.Wrap(err, "get host")
}
lockman.LockObject(ctx, host)
defer lockman.ReleaseObject(ctx, host)
usedDeviceMap := map[string]*SIsolatedDevice{}
for key, cnt := range devCount {
if cnt <= 0 {
continue
}
devType, sharingMode, model, ok := parseIsolatedDeviceRestoreKey(key)
if !ok {
return nil, errors.Errorf("invalid restore key %q", key)
}
devConfig := &api.IsolatedDeviceConfig{
Model: model,
DevType: devType,
SharingMode: sharingMode,
}
err := IsolatedDeviceManager.attachHostDeviceToGuestByModel(ctx, self, host, devConfig, userCred, usedDeviceMap, nil)
if err != nil {
return nil, errors.Wrap(err, "attachHostDeviceToGuestByModel")
}
}
return nil, nil
}
// 开机
func (self *SGuest) PerformStart(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input api.GuestPerformStartInput,
) (jsonutils.JSONObject, error) {
validStartStatuses := []string{api.VM_READY, api.VM_START_FAILED, api.VM_SAVE_DISK_FAILED, api.VM_SUSPEND, api.VM_KICKSTART_PENDING, api.VM_KICKSTART_FAILED}
if utils.IsInStringArray(self.Status, validStartStatuses) {
if err := self.ValidateEncryption(ctx, userCred); err != nil {
return nil, errors.Wrap(httperrors.ErrForbidden, "encryption key not accessible")
}
if !self.guestDisksStorageTypeIsShared() {
host, _ := self.GetHost()
guestStats, err := host.GetNotReadyGuestsStat()
if err != nil {
return nil, err
}
if float32(guestStats.GuestVcpuCount+self.VcpuCount) > host.GetVirtualCPUCount() {
log.Debugf("GuestPerformStart: guestStats: %s host: %f request: %d", jsonutils.Marshal(guestStats), host.GetVirtualCPUCount(), self.VcpuCount)
return nil, httperrors.NewInsufficientResourceError("host virtual cpu not enough")
}
if float32(guestStats.GuestVmemSize+self.VmemSize) > host.GetVirtualMemorySize() {
log.Debugf("GuestPerformStart: guestStats: %s host: %f request: %d", jsonutils.Marshal(guestStats), host.GetVirtualMemorySize(), self.VmemSize)
return nil, httperrors.NewInsufficientResourceError("host virtual memory not enough")
}
}
if self.isAllDisksReady() {
kwargs := jsonutils.Marshal(input).(*jsonutils.JSONDict)
driver, err := self.GetDriver()
if err != nil {
return nil, errors.Wrapf(err, "GetDriver")
}
err = driver.PerformStart(ctx, userCred, self, kwargs, "")
return nil, err
} else {
return nil, httperrors.NewInvalidStatusError("Some disk not ready")
}
} else {
return nil, httperrors.NewInvalidStatusError("Cannot do start server in status %s", self.Status)
}
}
func (self *SGuest) StartGuestDeployTask(
ctx context.Context, userCred mcclient.TokenCredential,
kwargs *jsonutils.JSONDict, action string, parentTaskId string,
) error {
self.SetStatus(ctx, userCred, api.VM_START_DEPLOY, "")
if kwargs == nil {
kwargs = jsonutils.NewDict()
}
kwargs.Add(jsonutils.NewString(action), "deploy_action")
taskName := "GuestDeployTask"
if self.BackupHostId != "" {
taskName = "HAGuestDeployTask"
}
task, err := taskman.TaskManager.NewTask(ctx, taskName, self, userCred, kwargs, parentTaskId, "", nil)
if err != nil {
return err
}
task.ScheduleRun(nil)
return nil
}
func (self *SGuest) EventNotify(ctx context.Context, userCred mcclient.TokenCredential, action noapi.SAction) {
detailsDecro := func(ctx context.Context, details *jsonutils.JSONDict) {
if action != notifyclient.ActionCreate && action != notifyclient.ActionRebuildRoot && action != notifyclient.ActionResetPassword {
return
}
meta, err := self.GetAllMetadata(ctx, userCred)
if err != nil {
return
}
details.Add(jsonutils.NewString(self.getNotifyIps()), "ips")
osName := meta[api.VM_METADATA_OS_NAME]
if osName == "Windows" {
details.Add(jsonutils.JSONTrue, "windows")
}
loginAccount := meta[api.VM_METADATA_LOGIN_ACCOUNT]
if len(loginAccount) > 0 {
details.Add(jsonutils.NewString(loginAccount), "login_account")
}
keypair := self.getKeypairName()
if len(keypair) > 0 {
details.Add(jsonutils.NewString(keypair), "keypair")
} else {
loginKey := meta[api.VM_METADATA_LOGIN_KEY]
if len(loginKey) > 0 {
passwd, err := utils.DescryptAESBase64(self.Id, loginKey)
if err == nil {
details.Add(jsonutils.NewString(passwd), "password")
}
}
}
}
var resourceType string
if self.Hypervisor == api.HYPERVISOR_BAREMETAL {
resourceType = noapi.TOPIC_RESOURCE_BAREMETAL
}
notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{
Obj: self,
ResourceType: resourceType,
Action: action,
ObjDetailsDecorator: detailsDecro,
AdvanceDays: 0,
})
}
func (self *SGuest) NotifyServerEvent(
ctx context.Context, userCred mcclient.TokenCredential, event string, priority notify.TNotifyPriority,
loginInfo bool, kwargs *jsonutils.JSONDict, notifyAdmin bool,
) {
meta, err := self.GetAllMetadata(ctx, userCred)
if err != nil {
return
}
if kwargs == nil {
kwargs = jsonutils.NewDict()
}
kwargs.Add(jsonutils.NewString(self.Name), "name")
kwargs.Add(jsonutils.NewString(self.Hypervisor), "hypervisor")
host, _ := self.GetHost()
if host != nil {
brand := host.GetBrand()
if brand == api.CLOUD_PROVIDER_ONECLOUD {
brand = api.ComputeI18nTable.Lookup(ctx, brand)
}
kwargs.Add(jsonutils.NewString(brand), "brand")
}
if loginInfo {
kwargs.Add(jsonutils.NewString(self.getNotifyIps()), "ips")
osName := meta[api.VM_METADATA_OS_NAME]
if osName == "Windows" {
kwargs.Add(jsonutils.JSONTrue, "windows")
}
loginAccount := meta[api.VM_METADATA_LOGIN_ACCOUNT]
if len(loginAccount) > 0 {
kwargs.Add(jsonutils.NewString(loginAccount), "account")
}
keypair := self.getKeypairName()
if len(keypair) > 0 {
kwargs.Add(jsonutils.NewString(keypair), "keypair")
} else {
loginKey := meta[api.VM_METADATA_LOGIN_KEY]
if len(loginKey) > 0 {
passwd, err := utils.DescryptAESBase64(self.Id, loginKey)
if err == nil {
kwargs.Add(jsonutils.NewString(passwd), "password")
}
}
}
}
notifyclient.NotifyWithCtx(ctx, []string{userCred.GetUserId()}, false, priority, event, kwargs)
if notifyAdmin {
notifyclient.SystemNotifyWithCtx(ctx, priority, event, kwargs)
}
}
func (self *SGuest) NotifyAdminServerEvent(ctx context.Context, event string, priority notify.TNotifyPriority) {
kwargs := jsonutils.NewDict()
kwargs.Add(jsonutils.NewString(self.Name), "name")
kwargs.Add(jsonutils.NewString(self.Hypervisor), "hypervisor")
tc, _ := self.GetTenantCache(ctx)
if tc != nil {
kwargs.Add(jsonutils.NewString(tc.Name), "tenant")
} else {
kwargs.Add(jsonutils.NewString(self.ProjectId), "tenant")
}
notifyclient.SystemNotifyWithCtx(ctx, priority, event, kwargs)
}
func (self *SGuest) StartGuestStopTask(ctx context.Context, userCred mcclient.TokenCredential, timeoutSecs *int, isForce, stopCharging bool, parentTaskId string) error {
if len(parentTaskId) == 0 {
self.SetStatus(ctx, userCred, api.VM_START_STOP, "")
}
params := jsonutils.NewDict()
if isForce {
params.Add(jsonutils.NewBool(isForce), "is_force")
}
if timeoutSecs != nil {
params.Add(jsonutils.NewInt(int64(*timeoutSecs)), "timeout")
} else {
timeout := options.Options.DefaultGuestStopTimeout
params.Add(jsonutils.NewInt(int64(timeout)), "timeout")
}
params.Add(jsonutils.NewBool(stopCharging), "stop_charging")
if len(parentTaskId) > 0 {
params.Add(jsonutils.JSONTrue, "subtask")
}
driver, err := self.GetDriver()
if err != nil {
return errors.Wrapf(err, "GetDriver")
}
shutdownMode := api.VM_SHUTDOWN_MODE_KEEP_CHARGING
if stopCharging && driver.IsSupportShutdownMode() {
shutdownMode = api.VM_SHUTDOWN_MODE_STOP_CHARGING
}
_, err = db.Update(self, func() error {
self.ShutdownMode = shutdownMode
return nil
})
if err != nil {
return errors.Wrapf(err, "db.Update")
}
return driver.StartGuestStopTask(self, ctx, userCred, params, parentTaskId)
}
func (self *SGuest) insertIso(imageId string, cdromOrdinal int64) bool {
cdrom := self.getCdrom(true, cdromOrdinal)
return cdrom.insertIso(imageId)
}
func (self *SGuest) InsertIsoSucc(cdromOrdinal int64, imageId string, path string, size int64, name string, bootIndex *int8) (*SGuestcdrom, bool) {
cdrom := self.getCdrom(false, cdromOrdinal)
return cdrom, cdrom.insertIsoSucc(imageId, path, size, name, bootIndex)
}
func (self *SGuest) GetDetailsIso(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
var cdromOrdinal int64 = 0
if query.Contains("ordinal") {
cdromOrdinal, _ = query.Int("ordinal")
}
cdrom := self.getCdrom(false, cdromOrdinal)
desc := jsonutils.NewDict()
if len(cdrom.ImageId) > 0 {
desc.Set("image_id", jsonutils.NewString(cdrom.ImageId))
desc.Set("status", jsonutils.NewString("inserting"))
}
if len(cdrom.Path) > 0 {
desc.Set("name", jsonutils.NewString(cdrom.Name))
desc.Set("size", jsonutils.NewInt(int64(cdrom.Size)))
desc.Set("status", jsonutils.NewString("ready"))
}
return desc, nil
}
// 获取Kickstart信息
func (self *SGuest) GetDetailsKickstart(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
result := jsonutils.NewDict()
kickstartConfig, err := self.GetKickstartConfig(ctx, userCred)
if err != nil {
return nil, httperrors.NewInternalServerError("Failed to get kickstart config: %v", err)
}
status := self.GetKickstartStatus(ctx, userCred)
attempt := self.GetMetadata(ctx, api.VM_METADATA_KICKSTART_ATTEMPT, userCred)
if attempt == "" {
attempt = "0"
}
kickstartType := self.GetKickstartType(ctx, userCred)
if kickstartConfig != nil {
configDict := jsonutils.Marshal(kickstartConfig)
result.Set("config", configDict)
} else {
result.Set("config", jsonutils.NewDict())
}
result.Set("status", jsonutils.NewString(status))
result.Set("attempt", jsonutils.NewString(attempt))
result.Set("type", jsonutils.NewString(kickstartType))
return result, nil
}
// 挂载ISO镜像
func (self *SGuest) PerformInsertiso(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if !utils.IsInStringArray(self.Hypervisor, []string{api.HYPERVISOR_KVM, api.HYPERVISOR_BAREMETAL}) {
return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.Hypervisor)
}
cdromOrdinal, _ := data.Int("cdrom_ordinal")
if cdromOrdinal < 0 {
return nil, httperrors.NewServerStatusError("invalid cdrom_ordinal: %d", cdromOrdinal)
}
cdrom := self.getCdrom(false, cdromOrdinal)
if cdrom != nil && len(cdrom.ImageId) > 0 {
return nil, httperrors.NewBadRequestError("CD-ROM not empty, please eject first")
}
imageId, _ := data.GetString("image_id")
image, err := parseIsoInfo(ctx, userCred, imageId)
if err != nil {
log.Errorln(err)
return nil, err
}
var bootIndex *int8
if data.Contains("boot_index") {
bd, _ := data.Int("boot_index")
bd8 := int8(bd)
bootIndex = &bd8
if isDup, err := self.isBootIndexDuplicated(bd8); err != nil {
return nil, err
} else if isDup {
return nil, httperrors.NewInputParameterError("boot index %d is duplicated", bd8)
}
}
if utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_READY}) {
err = self.StartInsertIsoTask(ctx, cdromOrdinal, image.Id, false, bootIndex, self.HostId, userCred, "")
return nil, err
} else {
return nil, httperrors.NewServerStatusError("Insert ISO not allowed in status %s", self.Status)
}
}
// 卸载ISO镜像
func (self *SGuest) PerformEjectiso(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if !utils.IsInStringArray(self.Hypervisor, []string{api.HYPERVISOR_KVM, api.HYPERVISOR_BAREMETAL}) {
return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.Hypervisor)
}
cdromOrdinal, _ := data.Int("cdrom_ordinal")
if cdromOrdinal < 0 {
return nil, httperrors.NewServerStatusError("invalid cdrom_ordinal: %d", cdromOrdinal)
}
cdrom := self.getCdrom(false, cdromOrdinal)
if cdrom == nil || len(cdrom.ImageId) == 0 {
return nil, httperrors.NewBadRequestError("No ISO to eject")
}
if utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_READY}) {
err := self.StartEjectisoTask(ctx, cdromOrdinal, userCred, "")
return nil, err
} else {
return nil, httperrors.NewServerStatusError("Eject ISO not allowed in status %s", self.Status)
}
}
func (self *SGuest) StartEjectisoTask(ctx context.Context, cdromOrdinal int64, userCred mcclient.TokenCredential, parentTaskId string) error {
data := jsonutils.NewDict()
data.Add(jsonutils.NewInt(cdromOrdinal), "cdrom_ordinal")
task, err := taskman.TaskManager.NewTask(ctx, "GuestEjectISOTask", self, userCred, data, parentTaskId, "", nil)
if err != nil {
return err
}
task.ScheduleRun(nil)
return nil
}
func (self *SGuest) StartInsertIsoTask(ctx context.Context, cdromOrdinal int64, imageId string, boot bool, bootIndex *int8, hostId string, userCred mcclient.TokenCredential, parentTaskId string) error {
self.insertIso(imageId, cdromOrdinal)
data := jsonutils.NewDict()
data.Add(jsonutils.NewString(imageId), "image_id")
data.Add(jsonutils.NewInt(cdromOrdinal), "cdrom_ordinal")
data.Add(jsonutils.NewString(hostId), "host_id")
if boot {
data.Add(jsonutils.JSONTrue, "boot")
}
if bootIndex != nil {
data.Add(jsonutils.NewInt(int64(*bootIndex)), "boot_index")
}
taskName := "GuestInsertIsoTask"
if self.BackupHostId != "" {
taskName = "HaGuestInsertIsoTask"
}
task, err := taskman.TaskManager.NewTask(ctx, taskName, self, userCred, data, parentTaskId, "", nil)
if err != nil {
return err
}
task.ScheduleRun(nil)
return nil
}
func (self *SGuest) insertVfd(imageId string, floppyOrdinal int64) bool {
floppy := self.getFloppy(true, floppyOrdinal)
return floppy.insertVfd(imageId)
}
func (self *SGuest) InsertVfdSucc(floppyOrdinal int64, imageId string, path string, size int64, name string) bool {
floppy := self.getFloppy(true, floppyOrdinal)
return floppy.insertVfdSucc(imageId, path, size, name)
}
func (self *SGuest) GetDetailsVfd(floppyOrdinal int64, userCred mcclient.TokenCredential) jsonutils.JSONObject {
floppy := self.getFloppy(false, floppyOrdinal)
desc := jsonutils.NewDict()
if len(floppy.ImageId) > 0 {
desc.Set("image_id", jsonutils.NewString(floppy.ImageId))
desc.Set("status", jsonutils.NewString("inserting"))
}
if len(floppy.Path) > 0 {
desc.Set("name", jsonutils.NewString(floppy.Name))
desc.Set("size", jsonutils.NewInt(int64(floppy.Size)))
desc.Set("status", jsonutils.NewString("ready"))
}
return desc
}
// 挂载软盘
func (self *SGuest) PerformInsertvfd(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data api.ServerInsertVfdInput) (jsonutils.JSONObject, error) {
if !utils.IsInStringArray(self.Hypervisor, []string{api.HYPERVISOR_KVM, api.HYPERVISOR_BAREMETAL}) {
return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.Hypervisor)
}
if data.FloppyOrdinal < 0 {
return nil, httperrors.NewServerStatusError("invalid floppy_ordinal: %d", data.FloppyOrdinal)
}
floppy := self.getFloppy(false, data.FloppyOrdinal)
if floppy != nil && len(floppy.ImageId) > 0 {
return nil, httperrors.NewBadRequestError("Floppy not empty, please eject first")
}
image, err := parseIsoInfo(ctx, userCred, data.ImageId)
if err != nil {
log.Errorln(err)
return nil, err
}
if utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_READY}) {
err = self.StartInsertVfdTask(ctx, data.FloppyOrdinal, image.Id, false, self.HostId, userCred, "")
return nil, err
} else {
return nil, httperrors.NewServerStatusError("Insert ISO not allowed in status %s", self.Status)
}
}
// 卸载软盘
func (self *SGuest) PerformEjectvfd(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data api.ServerEjectVfdInput) (jsonutils.JSONObject, error) {
if !utils.IsInStringArray(self.Hypervisor, []string{api.HYPERVISOR_KVM, api.HYPERVISOR_BAREMETAL}) {
return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.Hypervisor)
}
if data.FloppyOrdinal < 0 {
return nil, httperrors.NewServerStatusError("invalid floppy_ordinal: %d", data.FloppyOrdinal)
}
floppy := self.getFloppy(false, data.FloppyOrdinal)
if floppy == nil || len(floppy.ImageId) == 0 {
return nil, httperrors.NewBadRequestError("No VFD to eject")
}
if utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_READY}) {
err := self.StartEjectvfdTask(ctx, userCred, "")
return nil, err
} else {
return nil, httperrors.NewServerStatusError("Eject ISO not allowed in status %s", self.Status)
}
}
func (self *SGuest) StartEjectvfdTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
task, err := taskman.TaskManager.NewTask(ctx, "GuestEjectVFDTask", self, userCred, nil, parentTaskId, "", nil)
if err != nil {
return err
}
task.ScheduleRun(nil)
return nil
}
func (self *SGuest) StartInsertVfdTask(ctx context.Context, floppyOrdinal int64, imageId string, boot bool, hostId string, userCred mcclient.TokenCredential, parentTaskId string) error {
self.insertVfd(imageId, floppyOrdinal)
data := jsonutils.NewDict()
data.Add(jsonutils.NewString(imageId), "image_id")
data.Add(jsonutils.NewString(hostId), "host_id")
if boot {
data.Add(jsonutils.JSONTrue, "boot")
}
taskName := "GuestInsertVfdTask"
if self.BackupHostId != "" {
taskName = "HaGuestInsertVfdTask"
}
task, err := taskman.TaskManager.NewTask(ctx, taskName, self, userCred, data, parentTaskId, "", nil)
if err != nil {
return err
}
task.ScheduleRun(nil)
return nil
}
func (self *SGuest) RebalanceVirtualIsolatedDevices(ctx context.Context, userCred mcclient.TokenCredential) error {
guestDevs, err := self.GetGuestIsolatedDevices()
if err != nil {
return errors.Wrap(err, "guest get isolated devices")
}
if len(guestDevs) == 0 {
return nil
}
host, err := self.GetHost()
if err != nil {
return errors.Wrap(err, "guest get host")
}
var numaNodeBalance = true
detachDevs := make([]SGuestIsolatedDevice, 0)
originDevConfigs := make([]api.IsolatedDeviceConfig, 0)
for i := range guestDevs {
dev := guestDevs[i].GetIsolatedDevice()
if dev.SharingMode == api.DEVICE_SHARING_MODE_UNLIMITED {
originDevConfigs = append(originDevConfigs, api.IsolatedDeviceConfig{
Model: dev.Model,
DevType: dev.DevType,
MemoryRequest: guestDevs[i].DeviceMemorySize,
SmUtilLimit: guestDevs[i].SmUtilLimit,
})
detachDevs = append(detachDevs, guestDevs[i])
isBalance, err := host.VirtualDeviceNumaBalance(dev.Model, dev.NumaNode)
if err != nil {
return errors.Wrap(err, "VirtualDeviceNumaBalance")
}
if numaNodeBalance && !isBalance {
numaNodeBalance = false
}
}
}
log.Infof("Guest %s on host %s start virtual devices numa node balance %v", self.Id, host.Id, numaNodeBalance)
if !numaNodeBalance {
err := self.SetMetadata(ctx, api.VM_METADATA_VIRTUAL_ISOLATED_DEVICE_CONFIG, originDevConfigs, userCred)
if err != nil {
return errors.Wrap(err, "set metadata virtual isolated device config")
}
lockman.LockObject(ctx, host)
defer lockman.ReleaseObject(ctx, host)
detachedDevConfigs := []*api.IsolatedDeviceConfig{}
for i := 0; i < len(detachDevs); i++ {
err := self.detachIsolateDevice(ctx, userCred, &detachDevs[i])
if err != nil {
return errors.Wrapf(err, "detach device %s", detachDevs[i].GetId())
}
dev := detachDevs[i].GetIsolatedDevice()
detachedDevConfigs = append(detachedDevConfigs, &api.IsolatedDeviceConfig{
Model: dev.Model,
DevType: dev.DevType,
MemoryRequest: detachDevs[i].DeviceMemorySize,
SmUtilLimit: detachDevs[i].SmUtilLimit,
})
}
usedDeviceMap := map[string]*SIsolatedDevice{}
for i := range detachedDevConfigs {
err := IsolatedDeviceManager.attachHostDeviceToGuestByModel(ctx, self, host, detachedDevConfigs[i], userCred, usedDeviceMap, nil)
if err != nil {
return errors.Wrap(err, "attachHostDeviceToGuestByModel")
}
}
}
return nil
}
func (self *SGuest) StartGueststartTask(
ctx context.Context, userCred mcclient.TokenCredential,
data *jsonutils.JSONDict, parentTaskId string,
) error {
schedStart := self.Hypervisor == api.HYPERVISOR_KVM && self.guestDisksStorageTypeIsShared()
startFromCreate := false
if !gotypes.IsNil(data) {
startFromCreate = jsonutils.QueryBoolean(data, "start_from_create", false)
}
if options.Options.IgnoreNonrunningGuests {
host := HostManager.FetchHostById(self.HostId)
if !startFromCreate && host != nil && host.EnableNumaAllocate {
schedStart = true
}
}
if !startFromCreate && self.IsSchedulerNumaAllocate() {
// clean cpu numa pin
err := self.SetCpuNumaPin(ctx, userCred, nil, nil)
if err != nil {
return errors.Wrap(err, "clean cpu numa pin")
}
}
if options.Options.VirtualDeviceNumaBalance {
err := self.RebalanceVirtualIsolatedDevices(ctx, userCred)
if err != nil {
return errors.Wrap(err, "rebalance virtual isolated devices")
}
}
if schedStart {
return self.GuestSchedStartTask(ctx, userCred, data, parentTaskId)
} else {
return self.GuestNonSchedStartTask(ctx, userCred, data, parentTaskId)
}
}
func (self *SGuest) GuestSchedStartTask(
ctx context.Context, userCred mcclient.TokenCredential,
data *jsonutils.JSONDict, parentTaskId string,
) error {
self.SetStatus(ctx, userCred, api.VM_SCHEDULE, "")
task, err := taskman.TaskManager.NewTask(ctx, "GuestSchedStartTask", self, userCred, data,
parentTaskId, "", nil)
if err != nil {
return err
}
task.ScheduleRun(nil)
return nil
}
func (self *SGuest) GuestNonSchedStartTask(
ctx context.Context, userCred mcclient.TokenCredential,
data *jsonutils.JSONDict, parentTaskId string,
) error {
self.SetStatus(ctx, userCred, api.VM_START_START, "")
taskName := "GuestStartTask"
if self.BackupHostId != "" {
taskName = "HAGuestStartTask"
}
if self.IsSchedulerNumaAllocate() {
srcSchedCpuNumaPin := make([]schedapi.SCpuNumaPin, 0)
err := self.CpuNumaPin.Unmarshal(&srcSchedCpuNumaPin)
if err != nil {
return errors.Wrap(err, "unmarshal cpu_numa_pin")
}
// set cpu numa pin
err = self.SetCpuNumaPin(ctx, userCred, srcSchedCpuNumaPin, nil)
if err != nil {
return nil
}
}
task, err := taskman.TaskManager.NewTask(ctx, taskName, self, userCred, data, parentTaskId, "", nil)
if err != nil {
return err
}
task.ScheduleRun(nil)
return nil
}
func (self *SGuest) StartGuestCreateTask(ctx context.Context, userCred mcclient.TokenCredential, input *api.ServerCreateInput, pendingUsage quotas.IQuota, parentTaskId string) error {
if input.FakeCreate {
self.fixFakeServerInfo(ctx, userCred)
return nil
}
driver, err := self.GetDriver()
if err != nil {
return errors.Wrapf(err, "GetDriver")
}
return driver.StartGuestCreateTask(self, ctx, userCred, input.JSON(input), pendingUsage, parentTaskId)
}
func (self *SGuest) fixFakeServerInfo(ctx context.Context, userCred mcclient.TokenCredential) {
status := []string{api.VM_READY, api.VM_RUNNING}
rand.Seed(time.Now().Unix())
db.Update(self, func() error {
self.Status = status[rand.Intn(len(status))]
self.PowerStates = api.VM_POWER_STATES_ON
if self.Status == api.VM_READY {
self.PowerStates = api.VM_POWER_STATES_OFF
}
return nil
})
disks, _ := self.GetDisks()
for i := range disks {
db.Update(&disks[i], func() error {
disks[i].Status = api.DISK_READY
return nil
})
}
networks, _ := self.GetNetworks("")
for i := range networks {
if len(networks[i].IpAddr) > 0 {
continue
}
network, _ := networks[i].GetNetwork()
if network != nil {
db.Update(&networks[i], func() error {
networks[i].IpAddr, _ = network.GetFreeIP(ctx, userCred, nil, nil, "", api.IPAllocationRandom, false, api.AddressTypeIPv4)
return nil
})
}
}
if eip, _ := self.GetEipOrPublicIp(); eip != nil && len(eip.IpAddr) == 0 {
db.Update(eip, func() error {
if len(eip.NetworkId) > 0 {
if network, _ := eip.GetNetwork(); network != nil {
eip.IpAddr, _ = network.GetFreeIP(ctx, userCred, nil, nil, "", api.IPAllocationRandom, false, api.AddressTypeIPv4)
return nil
}
}
return nil
})
}
}
func (self *SGuest) StartSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
if len(parentTaskId) == 0 {
self.SetStatus(ctx, userCred, apis.STATUS_START_SYNC_STATUS, "")
}
driver, err := self.GetDriver()
if err != nil {
return errors.Wrapf(err, "GetDriver")
}
return driver.StartGuestSyncstatusTask(self, ctx, userCred, parentTaskId)
}
func (self *SGuest) StartAutoDeleteGuestTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
db.OpsLog.LogEvent(self, db.ACT_DELETE, "auto-delete after stop", userCred)
opts := api.ServerDeleteInput{}
return self.StartDeleteGuestTask(ctx, userCred, parentTaskId, opts)
}
func (self *SGuest) StartDeleteGuestTask(
ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string,
opts api.ServerDeleteInput,
) error {
params := jsonutils.NewDict()
params.Add(jsonutils.NewString(self.Status), "guest_status")
params.Update(jsonutils.Marshal(opts))
self.SetStatus(ctx, userCred, api.VM_START_DELETE, "")
if self.HostId != "" {
driver, err := self.GetDriver()
if err != nil {
return errors.Wrapf(err, "GetDriver")
}
return driver.StartDeleteGuestTask(ctx, userCred, self, params, parentTaskId)
} else {
task, err := taskman.TaskManager.NewTask(ctx, "GuestDeleteWithoutHostTask", self, userCred, params, parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask GuestDeleteWithoutHostTask")
}
task.ScheduleRun(nil)
return nil
}
}
// 清除虚拟机记录(仅数据库操作)
func (self *SGuest) PerformPurge(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
err := self.validateDeleteCondition(ctx, true)
if err != nil {
return nil, err
}
host, _ := self.GetHost()
if host != nil && host.GetEnabled() {
return nil, httperrors.NewInvalidStatusError("Cannot purge server on enabled host")
}
opts := api.ServerDeleteInput{
Purge: true,
}
err = self.StartDeleteGuestTask(ctx, userCred, "", opts)
return nil, err
}
func (self *SGuest) setKeypairId(userCred mcclient.TokenCredential, keypairId string) error {
diff, err := db.Update(self, func() error {
self.KeypairId = keypairId
return nil
})
if err != nil {
db.OpsLog.LogEvent(self, db.ACT_UPDATE, diff, userCred)
}
return err
}
// 重装系统(更换系统镜像)
func (self *SGuest) PerformRebuildRoot(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input *api.ServerRebuildRootInput,
) (*api.SGuest, error) {
driver, err := self.GetDriver()
if err != nil {
return nil, err
}
input, err = driver.ValidateRebuildRoot(ctx, userCred, self, input)
if err != nil {
return nil, err
}
if len(input.UserData) > 0 {
// validate UserData
if err := userdata.ValidateUserdata(input.UserData, self.OsType); err != nil {
return nil, httperrors.NewInputParameterError("Invalid userdata: %v", err)
}
self.setUserData(ctx, userCred, input.UserData)
}
if len(input.ImageId) > 0 {
img, err := CachedimageManager.getImageInfo(ctx, userCred, input.ImageId, false)
if err != nil {
return nil, httperrors.NewNotFoundError("failed to find %s", input.ImageId)
}
err = driver.ValidateImage(ctx, img)
if err != nil {
return nil, err
}
host, err := self.GetHost()
if err != nil {
return nil, errors.Wrapf(err, "GetHost")
}
region, err := host.GetRegion()
if err != nil {
return nil, errors.Wrapf(err, "GetRegion")
}
// compare os arch
if len(img.Properties["os_arch"]) > 0 && len(self.OsArch) > 0 && !apis.IsSameArch(self.OsArch, img.Properties["os_arch"]) {
return nil, httperrors.NewConflictError("root disk image(%s) and guest(%s) OsArch mismatch", img.Properties["os_arch"], self.OsArch)
} else if len(self.InstanceType) > 0 {
sku, _ := ServerSkuManager.FetchSkuByNameAndProvider(self.InstanceType, region.Provider, true)
if sku != nil && len(sku.CpuArch) > 0 && len(img.Properties["os_arch"]) > 0 && !apis.IsSameArch(img.Properties["os_arch"], sku.CpuArch) {
return nil, httperrors.NewConflictError("root disk image(%s) and sku(%s) architecture mismatch", img.Properties["os_arch"], sku.CpuArch)
}
}
diskCat := self.CategorizeDisks()
if gotypes.IsNil(diskCat.Root) {
return nil, httperrors.NewInputParameterError("no root disk is found")
}
if img.MinDiskMB == 0 || img.Status != imageapi.IMAGE_STATUS_ACTIVE {
return nil, httperrors.NewInputParameterError("invlid image")
}
if img.MinDiskMB > diskCat.Root.DiskSize {
return nil, httperrors.NewInputParameterError("image size exceeds root disk size")
}
osType, _ := img.Properties["os_type"]
osName := self.GetMetadata(ctx, "os_name", userCred)
if len(osName) == 0 && len(osType) == 0 && strings.ToLower(osType) != strings.ToLower(osName) {
return nil, httperrors.NewBadRequestError("Cannot switch OS between %s-%s", osName, osType)
}
input.ImageId = img.Id
}
templateId := self.GetTemplateId()
if templateId != input.ImageId && len(templateId) > 0 && len(input.ImageId) > 0 && !driver.IsRebuildRootSupportChangeUEFI() {
q := CachedimageManager.Query().In("id", []string{input.ImageId, templateId})
images := []SCachedimage{}
err := db.FetchModelObjects(CachedimageManager, q, &images)
if err != nil {
return nil, errors.Wrap(err, "FetchModelObjects")
}
if len(images) == 2 && images[0].UEFI != images[1].UEFI {
return nil, httperrors.NewUnsupportOperationError("Cannot rebuild root with a different UEFI image")
}
}
rebuildStatus, err := driver.GetRebuildRootStatus()
if err != nil {
return nil, httperrors.NewInputParameterError("%v", err)
}
if !driver.IsRebuildRootSupportChangeImage() && len(input.ImageId) > 0 {
if len(templateId) == 0 {
return nil, httperrors.NewBadRequestError("No template for root disk, cannot rebuild root")
}
if input.ImageId != templateId {
return nil, httperrors.NewInputParameterError("%s does not support rebuilding root with a different image", driver.GetHypervisor())
}
}
if !utils.IsInStringArray(self.Status, rebuildStatus) {
return nil, httperrors.NewInvalidStatusError("Cannot reset root in status %s", self.Status)
}
if self.Status == api.VM_READY && self.ShutdownMode == api.VM_SHUTDOWN_MODE_STOP_CHARGING {
return nil, httperrors.NewInvalidStatusError("Cannot reset root with %s", self.ShutdownMode)
}
autoStart := false
if input.AutoStart != nil {
autoStart = *input.AutoStart
}
var needStop = false
if self.Status == api.VM_RUNNING {
needStop = true
}
resetPasswd := input.ResetPassword
passwd := input.Password
if len(passwd) > 0 {
err = seclib2.ValidatePassword(passwd)
if err != nil {
return nil, errors.Wrap(err, "ValidatePassword")
}
}
if len(input.KeypairId) > 0 {
_, err := validators.ValidateModel(ctx, userCred, KeypairManager, &input.KeypairId)
if err != nil {
return nil, err
}
if self.KeypairId != input.KeypairId {
err = self.setKeypairId(userCred, input.KeypairId)
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
}
resetPasswd = true
} else {
err = self.setKeypairId(userCred, "")
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
}
allDisks := false
if input.AllDisks != nil {
allDisks = *input.AllDisks
}
input.ResetPassword = resetPasswd
if input.ResetPassword {
if len(input.LoginAccount) > 0 {
if len(input.LoginAccount) > 32 {
return nil, httperrors.NewInputParameterError("login_account exceeds 32 characters")
}
if err := GuestManager.ValidateNameLoginAccount(input.LoginAccount); err != nil {
return nil, err
}
}
}
return nil, self.StartRebuildRootTask(ctx, userCred, input.ImageId, needStop, autoStart, allDisks, &input.ServerDeployInputBase)
}
func (self *SGuest) GetTemplateId() string {
gdc := self.CategorizeDisks()
if gdc.Root != nil {
return gdc.Root.GetTemplateId()
}
return ""
}
func (self *SGuest) StartRebuildRootTask(ctx context.Context, userCred mcclient.TokenCredential, imageId string, needStop, autoStart bool, allDisk bool, deployInput *api.ServerDeployInputBase) error {
data := jsonutils.NewDict()
if len(imageId) == 0 {
imageId = self.GetTemplateId()
if len(imageId) == 0 {
return httperrors.NewBadRequestError("No template for root disk, cannot rebuild root")
}
}
data.Set("image_id", jsonutils.NewString(imageId))
if needStop {
data.Set("need_stop", jsonutils.JSONTrue)
}
if autoStart {
data.Set("auto_start", jsonutils.JSONTrue)
}
/*if resetPasswd {
data.Set("reset_password", jsonutils.JSONTrue)
} else {
data.Set("reset_password", jsonutils.JSONFalse)
}
if len(passwd) > 0 {
data.Set("password", jsonutils.NewString(passwd))
}*/
if allDisk {
data.Set("all_disks", jsonutils.JSONTrue)
} else {
data.Set("all_disks", jsonutils.JSONFalse)
}
data.Set("deploy_params", jsonutils.Marshal(deployInput))
self.SetStatus(ctx, userCred, api.VM_REBUILD_ROOT, "request start rebuild root")
task, err := taskman.TaskManager.NewTask(ctx, "GuestRebuildRootTask", self, userCred, data, "", "", nil)
if err != nil {
return err
}
task.ScheduleRun(nil)
return nil
}
func (self *SGuest) DetachDisk(ctx context.Context, disk *SDisk, userCred mcclient.TokenCredential) {
guestdisk := self.GetGuestDisk(disk.Id)
if guestdisk != nil {
guestdisk.Detach(ctx, userCred)
}
}
// 创建虚拟机磁盘
func (self *SGuest) PerformCreatedisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
diskSize := 0
disksConf := make([]*api.DiskConfig, 0)
diskSizes := make(map[string]int, 0)
diskDefArray, err := cmdline.FetchDiskConfigsByJSON(data)
if err != nil {
return nil, err
}
if len(diskDefArray) == 0 {
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_CREATE, "No Disk Info Provided", userCred, false)
return nil, httperrors.NewBadRequestError("No Disk Info Provided")
}
for diskIdx := 0; diskIdx < len(diskDefArray); diskIdx += 1 {
diskInfo, err := parseDiskInfo(ctx, userCred, diskDefArray[diskIdx])
if err != nil {
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_CREATE, err.Error(), userCred, false)
return nil, httperrors.NewBadRequestError("%v", err)
}
if len(diskInfo.Backend) == 0 {
diskInfo.Backend = self.getDefaultStorageType()
}
disksConf = append(disksConf, diskInfo)
if _, ok := diskSizes[diskInfo.Backend]; !ok {
diskSizes[diskInfo.Backend] = diskInfo.SizeMb
} else {
diskSizes[diskInfo.Backend] += diskInfo.SizeMb
}
diskSize += diskInfo.SizeMb
}
host, _ := self.GetHost()
if host == nil {
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_CREATE, "No valid host", userCred, false)
return nil, httperrors.NewBadRequestError("No valid host")
}
for backend, size := range diskSizes {
storage := host.GetLeastUsedStorage(backend)
if storage == nil {
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_CREATE, "No valid storage on current host", userCred, false)
return nil, httperrors.NewBadRequestError("No valid storage on current host")
}
if storage.GetCapacity() > 0 && storage.GetCapacity() < int64(size) {
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_CREATE, "Not eough storage space on current host", userCred, false)
return nil, httperrors.NewBadRequestError("Not eough storage space on current host")
}
}
pendingUsage := &SQuota{
Storage: diskSize,
}
keys, err := self.GetQuotaKeys()
if err != nil {
return nil, err
}
pendingUsage.SetKeys(keys)
err = quotas.CheckSetPendingQuota(ctx, userCred, pendingUsage)
if err != nil {
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_CREATE, err.Error(), userCred, false)
return nil, httperrors.NewOutOfQuotaError("%v", err)
}
lockman.LockObject(ctx, host)
defer lockman.ReleaseObject(ctx, host)
err = self.CreateDisksOnHost(ctx, userCred, host, disksConf, pendingUsage, false, options.Options.UseServerTagsForDisk, nil, nil, false)
if err != nil {
quotas.CancelPendingUsage(ctx, userCred, pendingUsage, pendingUsage, false)
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_CREATE, err.Error(), userCred, false)
return nil, httperrors.NewBadRequestError("%v", err)
}
err = self.StartGuestCreateDiskTask(ctx, userCred, disksConf, "")
return nil, err
}
func (self *SGuest) StartGuestCreateDiskTask(ctx context.Context, userCred mcclient.TokenCredential, disks []*api.DiskConfig, parentTaskId string) error {
data := jsonutils.NewDict()
data.Add(jsonutils.Marshal(disks), "disks")
task, err := taskman.TaskManager.NewTask(ctx, "GuestCreateDiskTask", self, userCred, data, parentTaskId, "", nil)
if err != nil {
return errors.Wrapf(err, "NewTask")
}
return task.ScheduleRun(nil)
}
// 卸载磁盘
func (self *SGuest) PerformDetachdisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerDetachDiskInput) (jsonutils.JSONObject, error) {
if len(input.DiskId) == 0 {
return nil, httperrors.NewMissingParameterError("disk_id")
}
diskObj, err := validators.ValidateModel(ctx, userCred, DiskManager, &input.DiskId)
if err != nil {
return nil, err
}
disk := diskObj.(*SDisk)
attached, err := self.isAttach2Disk(disk)
if err != nil {
return nil, httperrors.NewInternalServerError("check isAttach2Disk failed %s", err)
}
if !attached {
return nil, nil
}
driver, err := self.GetDriver()
if err != nil {
return nil, err
}
detachDiskStatus, err := driver.GetDetachDiskStatus()
if err != nil {
return nil, err
}
if input.KeepDisk && !driver.CanKeepDetachDisk() {
return nil, httperrors.NewInputParameterError("Cannot keep detached disk")
}
err = driver.ValidateDetachDisk(ctx, userCred, self, disk)
if err != nil {
return nil, err
}
if utils.IsInStringArray(self.Status, detachDiskStatus) {
self.SetStatus(ctx, userCred, api.VM_DETACH_DISK, "")
err = self.StartGuestDetachdiskTask(ctx, userCred, disk, input.KeepDisk, "", false, false)
return nil, err
}
return nil, httperrors.NewInvalidStatusError("Server in %s not able to detach disk", self.Status)
}
func (self *SGuest) StartGuestDetachdiskTask(
ctx context.Context, userCred mcclient.TokenCredential,
disk *SDisk, keepDisk bool, parentTaskId string, purge bool, syncDescOnly bool,
) error {
taskData := jsonutils.NewDict()
taskData.Add(jsonutils.NewString(disk.Id), "disk_id")
taskData.Add(jsonutils.NewBool(keepDisk), "keep_disk")
taskData.Add(jsonutils.NewBool(purge), "purge")
// finally reuse fw_only option
taskData.Add(jsonutils.NewBool(syncDescOnly), "sync_desc_only")
if utils.IsInStringArray(disk.Status, []string{api.DISK_INIT, api.DISK_ALLOC_FAILED}) {
//删除非正常状态下的disk
taskData.Add(jsonutils.JSONFalse, "keep_disk")
db.Update(disk, func() error {
disk.AutoDelete = true
return nil
})
}
driver, err := self.GetDriver()
if err != nil {
return errors.Wrapf(err, "GetDriver")
}
disk.SetStatus(ctx, userCred, api.DISK_DETACHING, "")
return driver.StartGuestDetachdiskTask(ctx, userCred, self, taskData, parentTaskId)
}
func (self *SGuest) GetReleasedIsolatedDevices(ctx context.Context, userCred mcclient.TokenCredential) ([]api.ServerReleasedIsolatedDevice, error) {
devs := make([]api.ServerReleasedIsolatedDevice, 0)
if ret := self.GetMetadata(ctx, api.VM_METADATA_RELEASED_DEVICES, userCred); ret == "" {
return devs, nil
}
obj := self.GetMetadataJson(ctx, api.VM_METADATA_RELEASED_DEVICES, userCred)
if obj == nil {
return nil, errors.Error("get medata json")
}
if err := obj.Unmarshal(&devs); err != nil {
return nil, errors.Wrapf(err, "unmarshal json string: %s", obj.String())
}
return devs, nil
}
func (self *SGuest) SetReleasedIsolatedDevices(ctx context.Context, userCred mcclient.TokenCredential, devs []SGuestIsolatedDevice) error {
records := make([]api.ServerReleasedIsolatedDevice, 0)
for i := range devs {
dev := devs[i].GetIsolatedDevice()
record := api.ServerReleasedIsolatedDevice{
DevType: dev.DevType,
Model: dev.Model,
GpuType: devs[i].GpuType,
SharingMode: dev.SharingMode,
MemoryRequest: devs[i].DeviceMemorySize,
}
records = append(records, record)
}
if err := self.SetMetadata(ctx, api.VM_METADATA_RELEASED_DEVICES, records, userCred); err != nil {
return errors.Wrap(err, "set metadata")
}
return nil
}
func (self *SGuest) DetachIsolatedDevices(ctx context.Context, userCred mcclient.TokenCredential, devs []SGuestIsolatedDevice) error {
host, _ := self.GetHost()
lockman.LockObject(ctx, host)
defer lockman.ReleaseObject(ctx, host)
for i := 0; i < len(devs); i++ {
// check first
dev := devs[i].GetIsolatedDevice()
if !dev.IsValidAttachDev() {
if devModel, err := IsolatedDeviceModelManager.GetByDevType(dev.DevType); err != nil {
msg := fmt.Sprintf("Can't separately detach dev type %s", dev.DevType)
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false)
return httperrors.NewBadRequestError("%s", msg)
} else {
if !devModel.HotPluggable.Bool() && self.GetStatus() == api.VM_RUNNING {
msg := fmt.Sprintf("dev type %s model %s unhotpluggable", dev.DevType, devModel.Model)
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false)
return httperrors.NewBadRequestError("%s", msg)
}
}
}
}
for i := 0; i < len(devs); i++ {
err := self.detachIsolateDevice(ctx, userCred, &devs[i])
if err != nil {
return errors.Wrapf(err, "detach device %s", devs[i].GetId())
}
}
return nil
}
// 卸载透传设备
func (self *SGuest) PerformDetachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.ServerDetachIsolatedDeviceInput) (jsonutils.JSONObject, error) {
if self.Hypervisor != api.HYPERVISOR_KVM && self.Hypervisor != api.HYPERVISOR_POD {
return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.Hypervisor)
}
if !input.IsForce {
if !utils.IsInStringArray(self.GetStatus(), []string{api.VM_READY, api.VM_RUNNING}) ||
(self.Hypervisor == api.HYPERVISOR_POD && self.GetStatus() != api.VM_READY) {
msg := fmt.Sprintf("Can't detach isolated device when guest is %s", self.GetStatus())
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false)
return nil, httperrors.NewInvalidStatusError("%s", msg)
}
} else {
if !utils.IsInStringArray(self.GetStatus(), []string{api.VM_READY, api.VM_UNKNOWN}) {
msg := fmt.Sprintf("Can't force detach isolated device when guest is %s", self.GetStatus())
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false)
return nil, httperrors.NewInvalidStatusError("%s", msg)
}
}
devs := make([]SGuestIsolatedDevice, 0)
if !input.DetachAll {
for i := range input.Devices {
device := input.Devices[i].Device
if input.Devices[i].Index == nil {
msg := "Missing isolated device index"
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false)
return nil, httperrors.NewBadRequestError("%s", msg)
}
index := *input.Devices[i].Index
iDev, err := IsolatedDeviceManager.FetchByIdOrName(ctx, userCred, device)
if err != nil {
msgFmt := "Isolated device %s not found"
msg := fmt.Sprintf(msgFmt, device)
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false)
return nil, httperrors.NewBadRequestError(msgFmt, device)
}
dev := iDev.(*SIsolatedDevice)
gdev, err := dev.GetGuestIsolatedDevice(self.Id, index)
if err != nil {
msg := err.Error()
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false)
return nil, httperrors.NewBadRequestError("%s", msg)
}
devs = append(devs, *gdev)
}
} else {
var err error
devs, err = self.GetGuestIsolatedDevices()
if err != nil {
msg := err.Error()
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false)
return nil, httperrors.NewBadRequestError("%s", msg)
}
}
if err := self.DetachIsolatedDevices(ctx, userCred, devs); err != nil {
return nil, err
}
if input.IsForce {
return nil, nil
}
return nil, self.StartIsolatedDevicesSyncTask(ctx, userCred, input.AutoStart, "")
}
func (self *SGuest) startDetachIsolateDeviceWithoutNic(ctx context.Context, userCred mcclient.TokenCredential, device string, index int) error {
iDev, err := IsolatedDeviceManager.FetchByIdOrName(ctx, userCred, device)
if err != nil {
msgFmt := "Isolated device %s not found"
msg := fmt.Sprintf(msgFmt, device)
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false)
return httperrors.NewBadRequestError(msgFmt, device)
}
dev := iDev.(*SIsolatedDevice)
gdev, err := dev.GetGuestIsolatedDevice(self.Id, index)
if err != nil {
msgFmt := "Isolated device %s index %d not found"
msg := fmt.Sprintf(msgFmt, device, index)
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false)
return httperrors.NewBadRequestError(msgFmt, device)
}
return self.DetachIsolatedDevices(ctx, userCred, []SGuestIsolatedDevice{*gdev})
}
func (self *SGuest) detachIsolateDevice(ctx context.Context, userCred mcclient.TokenCredential, gdev *SGuestIsolatedDevice) error {
if gdev.GuestId != self.Id {
msg := "Isolated device is not attached to this guest"
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false)
return httperrors.NewBadRequestError("%s", msg)
}
dev := gdev.GetIsolatedDevice()
drv, _ := self.GetDriver()
if err := drv.BeforeDetachIsolatedDevice(ctx, userCred, self, gdev); err != nil {
return errors.Wrapf(err, "BeforeDetachIsolatedDevice %s of guest %s", jsonutils.Marshal(gdev), self.GetId())
}
err := gdev.Detach(ctx, userCred)
if err != nil {
return err
}
db.OpsLog.LogEvent(self, db.ACT_GUEST_DETACH_ISOLATED_DEVICE, dev.GetShortDesc(ctx), userCred)
return nil
}
// 挂载透传设备
func (self *SGuest) PerformAttachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.ServerAttachIsolatedDeviceInput) (jsonutils.JSONObject, error) {
if self.Hypervisor != api.HYPERVISOR_KVM && self.Hypervisor != api.HYPERVISOR_POD {
return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.Hypervisor)
}
if !utils.IsInStringArray(self.GetStatus(), []string{api.VM_READY, api.VM_RUNNING}) ||
(self.Hypervisor == api.HYPERVISOR_POD && self.GetStatus() != api.VM_READY) {
msg := fmt.Sprintf("Can't attach isolated device when guest is %v", self.GetStatus())
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_ATTACH_ISOLATED_DEVICE, msg, userCred, false)
return nil, httperrors.NewInvalidStatusError("%s", msg)
}
var err error
if input.GpuType != "" && !utils.IsInStringArray(input.GpuType, []string{api.GPU_VGA, api.GPU_HPC}) {
return nil, httperrors.NewInputParameterError("gpu_type %s not vaild", input.GpuType)
}
if input.MemoryRequest != nil && *input.MemoryRequest <= 0 {
return nil, httperrors.NewBadRequestError("guest attach gpu memory request count must > 0")
}
if input.Device != "" {
err = self.StartAttachIsolatedDeviceGpuOrUsb(ctx, userCred, input.Device, input.GpuType, input.MemoryRequest, input.AutoStart)
} else if input.Model != "" {
if !utils.IsInStringArray(input.SharingMode, api.VAILD_SHARING_MODES) {
return nil, httperrors.NewInputParameterError("shaing_mode %s not valid", input.SharingMode)
}
var count int = 1
if input.Count != nil {
count = *input.Count
}
if count < 1 {
return nil, httperrors.NewBadRequestError("guest attach gpu count must > 0")
}
input.Count = &count
err = self.StartAttachIsolatedDevices(ctx, userCred, input.Model, input.GpuType, input.SharingMode, count, input.MemoryRequest, input.AutoStart)
} else {
return nil, httperrors.NewMissingParameterError("device||model")
}
if err != nil {
return nil, err
}
return nil, nil
}
func (self *SGuest) StartAttachIsolatedDevices(ctx context.Context, userCred mcclient.TokenCredential, devModel, gpuType, sharingMode string, count int, memoryRequest *int, autoStart bool) error {
if err := self.startAttachIsolatedDevices(ctx, userCred, devModel, gpuType, sharingMode, count, memoryRequest); err != nil {
return err
}
// perform post attach task
return self.StartIsolatedDevicesSyncTask(ctx, userCred, autoStart, "")
}
func (self *SGuest) AttachIsolatedDevices(ctx context.Context, userCred mcclient.TokenCredential, devModelCount map[string]int, gpuType, sharingMode string, memoryRequest *int) error {
host, _ := self.GetHost()
lockman.LockObject(ctx, host)
defer lockman.ReleaseObject(ctx, host)
attachedGpus, err := self.GetGuestIsolatedDevices()
if err != nil {
return errors.Wrap(err, "get isolated devices")
}
attachedAddrs := map[string]struct{}{}
for i := range attachedGpus {
dev := attachedGpus[i].GetIsolatedDevice()
attachedAddrs[dev.Addr] = struct{}{}
gdev := attachedGpus[i].GetIsolatedDevice()
if sharingMode == api.DEVICE_SHARING_MODE_MDEV {
if gdev.SharingMode == api.DEVICE_SHARING_MODE_MDEV {
return httperrors.NewBadRequestError("Nvidia vgpu count exceed > 1")
} else if gdev.DevType == api.GPU_TYPE {
return httperrors.NewBadRequestError("Nvidia vgpu can't passthrough with other gpus")
}
}
}
for devModel, count := range devModelCount {
devs, err := IsolatedDeviceManager.GetAvailableIsolatedDeviceOnHost(host.Id, devModel, sharingMode)
if err != nil {
return httperrors.NewInternalServerError("fetch gpu failed %s", err)
}
if len(devs) == 0 {
return httperrors.NewBadRequestError("required %d %s isolated devices on host %s, but not enough are available", count, devModel, host.GetName())
}
avaDevs := make([]SIsolatedDevice, 0)
avaDevCnt := 0
for i := range devs {
dev := devs[i]
if _, ok := attachedAddrs[dev.Addr]; ok {
continue
}
if dev.SharingMode == api.DEVICE_SHARING_MODE_HAMI {
if memoryRequest == nil || *memoryRequest <= 0 {
return httperrors.NewBadRequestError("dev sharing_mode %s memory request invalid", dev.SharingMode)
}
if dev.IsEnough(*memoryRequest) {
avaDevs = append(avaDevs, dev)
avaDevCnt += 1
}
} else {
cnt, err := dev.getAllocatedCount()
if err != nil {
return errors.Wrap(err, "getAllocatedCount")
}
if dev.VirtualNum > cnt {
avaCnt := dev.VirtualNum - cnt
avaDevCnt += avaCnt
for j := 0; j < avaCnt; j++ {
avaDevs = append(avaDevs, dev)
}
}
}
}
if avaDevCnt < count {
return httperrors.NewInsufficientResourceError("Available device count %d less then request count %d", avaDevCnt, count)
}
dev := avaDevs[0]
if !dev.HotPluggable && self.GetStatus() == api.VM_RUNNING {
return httperrors.NewBadRequestError("dev type %s model %s unhotpluggable", dev.DevType, dev.Model)
}
for k := 0; k < count; k++ {
if err := self.attachIsolatedDevice(ctx, userCred, &avaDevs[k], nil, nil, memoryRequest, gpuType); err != nil {
return errors.Wrapf(err, "attach device %s", avaDevs[k].GetId())
}
}
}
go host.ClearSchedDescCache()
return nil
}
func (self *SGuest) startAttachIsolatedDevices(ctx context.Context, userCred mcclient.TokenCredential, devModel, gpuType, sharingMode string, count int, memoryRequest *int) error {
return self.AttachIsolatedDevices(ctx, userCred, map[string]int{devModel: count}, gpuType, sharingMode, memoryRequest)
}
func (self *SGuest) StartAttachIsolatedDeviceGpuOrUsb(ctx context.Context, userCred mcclient.TokenCredential, device, gpuType string, memoryRequest *int, autoStart bool) error {
if err := self.startAttachIsolatedDevGeneral(ctx, userCred, device, gpuType, memoryRequest); err != nil {
return err
}
// perform post attach task
return self.StartIsolatedDevicesSyncTask(ctx, userCred, autoStart, "")
}
func (self *SGuest) startAttachIsolatedDevGeneral(ctx context.Context, userCred mcclient.TokenCredential, device, gpuType string, memoryRequest *int) error {
iDev, err := IsolatedDeviceManager.FetchByIdOrName(ctx, userCred, device)
if err != nil {
msgFmt := "Isolated device %s not found"
msg := fmt.Sprintf(msgFmt, device)
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_ATTACH_ISOLATED_DEVICE, msg, userCred, false)
return httperrors.NewBadRequestError(msgFmt, device)
}
dev := iDev.(*SIsolatedDevice)
gdevs, err := self.GetGuestIsolatedDevices()
if err != nil {
return err
}
for i := range gdevs {
if gdevs[i].IsolatedDeviceId == dev.Id {
return httperrors.NewBadRequestError("device %s is already in used by guest %s", dev.Id, self.GetName())
}
}
if !dev.HotPluggable && self.GetStatus() == api.VM_RUNNING {
return httperrors.NewBadRequestError("dev type %s model %s unhotpluggable", dev.DevType, dev.Model)
}
if !utils.IsInStringArray(self.GetStatus(), []string{api.VM_READY, api.VM_RUNNING}) {
return httperrors.NewInvalidStatusError("Can't attach isolated device when status is %q", self.GetStatus())
}
reqCnt := 1
if dev.SharingMode == api.DEVICE_SHARING_MODE_HAMI {
if memoryRequest == nil || *memoryRequest <= 0 {
return httperrors.NewBadRequestError("Dev sharing_mode %s memory request invalid", dev.SharingMode)
}
reqCnt = *memoryRequest
}
if !dev.IsEnough(reqCnt) {
return httperrors.NewBadRequestError("Dev %s is not enough", dev.GetName())
}
if dev.SharingMode == api.DEVICE_SHARING_MODE_MDEV {
devs, err := self.GetGuestIsolatedDevices()
if err != nil {
return errors.Wrap(err, "get isolated devices")
}
for i := range devs {
gdev := devs[i].GetIsolatedDevice()
if gdev.SharingMode == api.DEVICE_SHARING_MODE_MDEV {
return httperrors.NewBadRequestError("Nvidia vgpu count exceed > 1")
} else if gdev.DevType == api.GPU_TYPE {
return httperrors.NewBadRequestError("Nvidia vgpu can't passthrough with other gpus")
}
}
}
host, _ := self.GetHost()
lockman.LockObject(ctx, host)
defer lockman.ReleaseObject(ctx, host)
err = self.attachIsolatedDevice(ctx, userCred, dev, nil, nil, memoryRequest, gpuType)
var msg string
if err != nil {
msg = err.Error()
} else {
go host.ClearSchedDescCache()
}
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_GUEST_ATTACH_ISOLATED_DEVICE, msg, userCred, err == nil)
return err
}
func (self *SGuest) attachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, dev *SIsolatedDevice, networkIndex *int, diskIndex *int8, memoryRequest *int, gpuType string) error {
if dev.IsFull() {
return fmt.Errorf("Isolated device already allocated")
}
if dev.HostId != self.HostId {
return fmt.Errorf("Isolated device and guest are not located in the same host")
}
lockman.LockObject(ctx, self)
defer lockman.ReleaseObject(ctx, self)
// guest isolated device attach
guestIsolatedDevice := SGuestIsolatedDevice{}
guestIsolatedDevice.SetModelManager(GuestIsolatedDeviceManager, &guestIsolatedDevice)
guestIsolatedDevice.GuestId = self.Id
guestIsolatedDevice.IsolatedDeviceId = dev.Id
guestIsolatedDevice.Index = self.getIsolatedDeviceIndex()
if networkIndex != nil {
guestIsolatedDevice.NetworkIndex = *networkIndex
}
if diskIndex != nil {
guestIsolatedDevice.DiskIndex = *diskIndex
}
if dev.SharingMode == api.DEVICE_SHARING_MODE_HAMI && memoryRequest != nil {
guestIsolatedDevice.DeviceMemorySize = *memoryRequest
guestIsolatedDevice.SmUtilLimit = int((float64(*memoryRequest) / float64(dev.MemorySize)) * 100.0)
}
if utils.IsInStringArray(gpuType, []string{api.GPU_HPC, api.GPU_VGA}) {
guestIsolatedDevice.GpuType = gpuType
}
drv, _ := self.GetDriver()
if err := drv.BeforeAttachIsolatedDevice(ctx, userCred, self, &guestIsolatedDevice); err != nil {
return errors.Wrapf(err, "BeforeAttachIsolatedDevice %s of guest %s", jsonutils.Marshal(guestIsolatedDevice), self.GetId())
}
err := GuestIsolatedDeviceManager.TableSpec().Insert(ctx, &guestIsolatedDevice)
if err != nil {
return err
}
db.OpsLog.LogEvent(self, db.ACT_GUEST_ATTACH_ISOLATED_DEVICE, dev.GetShortDesc(ctx), userCred)
return nil
}
// 设置透传设备
func (self *SGuest) PerformSetIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.SetIsolatedDeviceInput) (jsonutils.JSONObject, error) {
if self.Hypervisor != api.HYPERVISOR_KVM && self.Hypervisor != api.HYPERVISOR_POD {
return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.Hypervisor)
}
if !utils.IsInStringArray(self.GetStatus(), []string{api.VM_READY, api.VM_RUNNING}) ||
(self.Hypervisor == api.HYPERVISOR_POD && self.GetStatus() != api.VM_READY) {
return nil, httperrors.NewInvalidStatusError("Can't set isolated device when guest is %s", self.GetStatus())
}
var addDevs = input.AddDevices
var delDevs = input.DelDevices
// detach first
for i := 0; i < len(delDevs); i++ {
err := self.startDetachIsolateDeviceWithoutNic(ctx, userCred, delDevs[i].Device, delDevs[i].Index)
if err != nil {
return nil, err
}
}
for i := 0; i < len(addDevs); i++ {
if addDevs[i].GpuType != "" && !utils.IsInStringArray(addDevs[i].GpuType, []string{api.GPU_VGA, api.GPU_HPC}) {
return nil, httperrors.NewInputParameterError("gpu_type %s not vaild", addDevs[i].GpuType)
}
err := self.startAttachIsolatedDevGeneral(ctx, userCred, addDevs[i].Device, addDevs[i].GpuType, addDevs[i].MemoryRequest)
if err != nil {
return nil, err
}
}
return nil, self.StartIsolatedDevicesSyncTask(ctx, userCred, input.AutoStart, "")
}
func (self *SGuest) StartIsolatedDevicesSyncTask(ctx context.Context, userCred mcclient.TokenCredential, autoStart bool, parentId string) error {
if self.GetStatus() == api.VM_RUNNING {
autoStart = false
}
data := jsonutils.Marshal(map[string]interface{}{
"auto_start": autoStart,
}).(*jsonutils.JSONDict)
if task, err := taskman.TaskManager.NewTask(ctx, "GuestIsolatedDeviceSyncTask", self, userCred, data, parentId, "", nil); err != nil {
return err
} else {
return task.ScheduleRun(nil)
}
}
func (self *SGuest) findGuestnetworkByInfo(info api.ServerNetworkInfo) (*SGuestnetwork, error) {
if len(info.IpAddr) > 0 {
gn, err := self.GetGuestnetworkByIp(info.IpAddr)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, httperrors.NewNotFoundError("ip %s not found", info.IpAddr)
}
return nil, httperrors.NewGeneralError(err)
}
return gn, nil
} else if len(info.Ip6Addr) > 0 {
gn, err := self.GetGuestnetworkByIp6(info.Ip6Addr)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, httperrors.NewNotFoundError("ipv6 %s not found", info.Ip6Addr)
}
return nil, httperrors.NewGeneralError(err)
}
return gn, nil
} else if len(info.Mac) > 0 {
gn, err := self.GetGuestnetworkByMac(info.Mac)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, httperrors.NewNotFoundError("mac %s not found", info.Mac)
}
return nil, httperrors.NewGeneralError(err)
}
return gn, nil
} else {
gns, err := self.GetNetworks("")
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
if info.Index >= 0 && info.Index < len(gns) {
return &gns[info.Index], nil
} else if info.Index >= 0 {
return nil, httperrors.NewNotFoundError("nic at index %d not found", info.Index)
}
return nil, httperrors.NewInputParameterError("no either ip_addr, ip6_addr, mac or index specified")
}
}
func (self *SGuest) getReuseAddr(gn *SGuestnetwork) string {
if self.GetHypervisor() != api.HYPERVISOR_BAREMETAL {
return ""
}
host, _ := self.GetHost()
hostNics := host.GetNics()
for _, hn := range hostNics {
if hn.GetMac().String() == gn.MacAddr {
return hn.IpAddr
}
}
return ""
}
// Change IPaddress of a guestnetwork
// first detach the network, then attach a network with identity mac address but different IP configurations
// TODO change IP address of a teaming NIC may fail!!
func (self *SGuest) PerformChangeIpaddr(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input api.ServerChangeIpaddrInput,
) (jsonutils.JSONObject, error) {
if self.Status != api.VM_READY && self.Status != api.VM_RUNNING && self.Status != api.VM_BLOCK_STREAM {
return nil, httperrors.NewInvalidStatusError("Cannot change network ip_addr in status %s", self.Status)
}
reserve := (input.Reserve != nil && *input.Reserve)
gn, err := self.findGuestnetworkByInfo(input.ServerNetworkInfo)
if err != nil {
return nil, errors.Wrap(err, "findGuestnetworkByInfo")
}
var conf *api.NetworkConfig
if input.NetConf != nil {
conf = input.NetConf
} else if len(input.NetDesc) > 0 {
netDescJson, err := jsonutils.ParseString(input.NetDesc)
if err != nil {
netDescJson = jsonutils.NewString(input.NetDesc)
}
netConf, err := cmdline.ParseNetworkConfigByJSON(netDescJson, -1)
if err != nil {
return nil, httperrors.NewInputParameterError("failed to parse net_desc %s: %s", input.NetDesc, err)
}
conf = netConf
} else {
return nil, httperrors.NewMissingParameterError("net_desc/net_conf")
}
if conf.BwLimit == 0 {
conf.BwLimit = gn.BwLimit
}
if conf.TxBwLimit == 0 {
conf.TxBwLimit = gn.TxBwLimit
}
if conf.RxBwLimit == 0 {
conf.RxBwLimit = gn.RxBwLimit
}
if conf.Index == 0 {
conf.Index = int(gn.Index)
}
conf, err = parseNetworkInfo(ctx, userCred, conf)
if err != nil {
return nil, httperrors.NewInputParameterError("parseNetworkInfo fail: %s", err)
}
if conf.StrictIPv6 && len(conf.Address) > 0 {
// strict ipv6 network, can't add ipv4 address
return nil, httperrors.NewBadRequestError("guest network has no ipv4 address")
}
reuseV4 := ""
if conf.Address == gn.IpAddr {
// 允许IPv4地址不变只改IPv6地址
reuseV4 = conf.Address
}
reuseV6 := ""
if gn.Ip6Addr != "" && conf.Address6 != "" {
inputIP := net.ParseIP(conf.Address6)
gnIP := net.ParseIP(gn.Ip6Addr)
if string(inputIP) == string(gnIP) {
conf.Address6 = gn.Ip6Addr
reuseV6 = gn.Ip6Addr
}
}
err = isValidNetworkInfo(ctx, userCred, conf, reuseV4, reuseV6)
if err != nil {
return nil, httperrors.NewInputParameterError("isValidNetworkInfo fail: %s", err)
}
if len(conf.Network) == 0 {
return nil, httperrors.NewInputParameterError("no specific network")
}
netObj, err := NetworkManager.FetchById(conf.Network)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, httperrors.NewResourceNotFoundError2("network", conf.Network)
} else {
return nil, errors.Wrapf(err, "NetworkManager.FetchById %s", conf.Network)
}
}
targetNetwork := netObj.(*SNetwork)
// host, _ := self.GetHost()
ngn, err := func() (*SGuestnetwork, error) {
lockman.LockObject(ctx, targetNetwork)
defer lockman.ReleaseObject(ctx, targetNetwork)
if len(conf.Mac) > 0 {
if conf.Mac != gn.MacAddr {
if self.Status != api.VM_READY {
// change mac
return nil, httperrors.NewInvalidStatusError("cannot change mac when guest is running")
}
// check mac duplication
cnt, err := GuestnetworkManager.Query().Equals("mac_addr", conf.Mac).CountWithError()
if err != nil {
return nil, httperrors.NewInternalServerError("check mac uniqueness failed %s", err)
}
if cnt > 0 {
return nil, httperrors.NewConflictError("mac addr %s has been occupied", conf.Mac)
}
}
} else {
conf.Mac = gn.MacAddr
}
if conf.Mac == gn.MacAddr && conf.Address == gn.IpAddr {
if len(gn.Ip6Addr) == 0 && len(conf.Address6) == 0 && !conf.RequireIPv6 {
return nil, nil
} else if len(gn.Ip6Addr) > 0 && conf.Address6 == gn.Ip6Addr {
return nil, nil
}
// reserve = true
}
if conf.StrictIPv6 {
conf.Address = ""
} else if len(conf.Address) == 0 || conf.Address != gn.IpAddr {
// need to allocate new address
addr4, err := targetNetwork.GetFreeIP(ctx, userCred, nil, nil, conf.Address, api.IPAllocationDirection(targetNetwork.AllocPolicy), reserve, api.AddressTypeIPv4)
if err != nil {
return nil, errors.Wrap(err, "GetFreeIPv4")
}
if len(conf.Address) > 0 && conf.RequireDesignatedIP && conf.Address != addr4 {
return nil, httperrors.NewConflictError("addr %s has been occupied", conf.Address)
}
conf.Address = addr4
}
if (conf.Address6 == "" && conf.RequireIPv6) || (len(conf.Address6) > 0 && conf.Address6 != gn.Ip6Addr) {
// need to allocate new IPv6 address
addr6, err := targetNetwork.GetFreeIP(ctx, userCred, nil, nil, conf.Address6, api.IPAllocationNone, reserve, api.AddressTypeIPv6)
if err != nil {
return nil, errors.Wrap(err, "GetFreeIPv6")
}
if len(conf.Address6) > 0 && conf.RequireDesignatedIP && conf.Address6 != addr6 {
return nil, httperrors.NewConflictError("addr %s has been occupied", conf.Address6)
}
conf.Address6 = addr6
}
ngn := *gn
_, err := db.Update(&ngn, func() error {
ngn.NetworkId = conf.Network
ngn.MacAddr = conf.Mac
ngn.IpAddr = conf.Address
ngn.Ip6Addr = conf.Address6
return nil
})
if err != nil {
return nil, errors.Wrap(err, "Update")
}
return &ngn, nil
}()
if err != nil {
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_VM_CHANGE_NIC, err, userCred, false)
return nil, err
}
if ngn == nil {
return nil, nil
}
//Get the detailed description of the NIC
networkJsonDesc := ngn.getJsonDesc()
newIpAddr := networkJsonDesc.Ip
newMacAddr := networkJsonDesc.Mac
newMaskLen := networkJsonDesc.Masklen
newGateway := networkJsonDesc.Gateway
ipMask := ""
if networkJsonDesc.Ip != "" {
ipMask = fmt.Sprintf("%s/%d", newIpAddr, newMaskLen)
}
ip6Mask := ""
if networkJsonDesc.Ip6 != "" {
ip6Mask = fmt.Sprintf("%s/%d", networkJsonDesc.Ip6, networkJsonDesc.Masklen6)
}
notes := gn.GetShortDesc(ctx)
if gn != nil {
if gn.IpAddr != ngn.IpAddr {
notes.Add(jsonutils.NewString(gn.IpAddr), "prev_ip")
}
if gn.Ip6Addr != ngn.Ip6Addr {
notes.Add(jsonutils.NewString(gn.Ip6Addr), "prev_ip6")
}
}
db.OpsLog.LogEvent(self, db.ACT_CHANGE_IPADDR, notes, userCred)
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_VM_CHANGE_NIC, notes, userCred, true)
restartNetwork := (input.RestartNetwork != nil && *input.RestartNetwork)
taskData := jsonutils.NewDict()
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, err := ngn.GetNetwork()
if err != nil {
return nil, errors.Wrapf(err, "GetNetwork")
}
taskData.Set("is_vpc_network", jsonutils.NewBool(net.isOneCloudVpcNetwork()))
if len(ipMask) > 0 {
taskData.Set("ip_mask", jsonutils.NewString(ipMask))
taskData.Set("gateway", jsonutils.NewString(newGateway))
}
if len(ip6Mask) > 0 {
taskData.Set("ip6_mask", jsonutils.NewString(ip6Mask))
taskData.Set("gateway6", jsonutils.NewString(networkJsonDesc.Gateway6))
}
if self.Status == api.VM_BLOCK_STREAM {
taskData.Set("in_block_stream", jsonutils.JSONTrue)
}
self.SetStatus(ctx, userCred, api.VM_RESTART_NETWORK, "restart network")
}
if input.NoSync != nil && *input.NoSync {
return nil, nil
}
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) {
if !utils.IsInStringArray(self.Status, []string{api.VM_READY, api.VM_RUNNING}) && !input.IsForce() {
return nil, httperrors.NewInvalidStatusError("Cannot detach network in status %s", self.Status)
}
driver, err := self.GetDriver()
if err != nil {
return nil, err
}
err = driver.ValidateDetachNetwork(ctx, userCred, self)
if err != nil {
if !input.IsForce() {
return nil, errors.Wrap(err, "ValidateDetachNetwork")
} else {
log.Errorf("ValidateDetachNetwork fail %s, ignore by force", err)
}
}
var gns []SGuestnetwork
if len(input.NetId) > 0 {
netObj, err := validators.ValidateModel(ctx, userCred, NetworkManager, &input.NetId)
if err != nil {
return nil, err
}
gns, err = self.GetNetworks(netObj.GetId())
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
} else if len(input.IpAddr) > 0 || len(input.Ip6Addr) > 0 {
var gn *SGuestnetwork
var err error
if len(input.IpAddr) > 0 {
gn, err = self.GetGuestnetworkByIp(input.IpAddr)
} else if len(input.Ip6Addr) > 0 {
gn, err = self.GetGuestnetworkByIp6(input.Ip6Addr)
}
if err != nil {
if err == sql.ErrNoRows {
return nil, httperrors.NewNotFoundError("ip %s not found", input.IpAddr)
}
return nil, httperrors.NewGeneralError(err)
}
gns = []SGuestnetwork{*gn}
} else if len(input.Mac) > 0 {
gn, err := self.GetGuestnetworkByMac(input.Mac)
if err != nil {
if err == sql.ErrNoRows {
return nil, httperrors.NewNotFoundError("mac %s not found", input.Mac)
}
return nil, httperrors.NewGeneralError(err)
}
gns = []SGuestnetwork{*gn}
} else {
return nil, httperrors.NewMissingParameterError("net_id")
}
removeNics := make(map[string]*SGuestnetwork)
for i := range gns {
removeNics[gns[i].MacAddr] = &gns[i]
}
slaveNics, err := self.GetSlaveNetworks()
if err != nil {
return nil, errors.Wrap(err, "GetSlaveNetworks")
}
for i := range slaveNics {
teamMac := slaveNics[i].TeamWith
if len(teamMac) > 0 {
if _, ok := removeNics[teamMac]; ok {
// to remove slave's master NIC, should also remove slave itself
if _, ok2 := removeNics[slaveNics[i].MacAddr]; !ok2 {
// otherwise, report the error
return nil, errors.Wrap(errors.ErrInvalidStatus, "")
}
}
}
}
err = self.detachNetworks(ctx, userCred, gns, input.Reserve)
if err != nil {
return nil, errors.Wrap(err, "detachNetworks")
}
{
// 修正缺省网关
err := self.fixDefaultGateway(ctx, userCred)
if err != nil {
log.Errorf("fixDefaultGateway fail %s", err)
}
}
if !input.IsForce() && input.DisableSyncConfig != nil && !*input.DisableSyncConfig {
if self.Status == api.VM_READY {
err := self.StartGuestDeployTask(ctx, userCred, nil, "deploy", "")
if err != nil {
return nil, errors.Wrap(err, "StartGuestDeployTask")
}
} else {
err := self.StartSyncTask(ctx, userCred, false, "")
if err != nil {
return nil, errors.Wrap(err, "StartSyncTask")
}
}
}
return nil, nil
}
func (guest *SGuest) fixDefaultGatewayByNics(ctx context.Context, userCred mcclient.TokenCredential, nics []SGuestnetwork) (bool, error) {
defaultGwCnt := 0
for i := range nics {
if nics[i].Virtual || len(nics[i].TeamWith) > 0 {
continue
}
if nics[i].IsDefault {
defaultGwCnt++
}
}
if defaultGwCnt == 1 {
return false, nil
}
nicList := netutils2.SNicInfoList{}
for i := range nics {
if nics[i].Virtual || len(nics[i].TeamWith) > 0 {
continue
}
net, _ := nics[i].GetNetwork()
if net != nil {
nicList = nicList.Add(nics[i].MacAddr, nics[i].IpAddr, net.GuestGateway, nics[i].Ip6Addr, net.GuestGateway6, nics[i].IsDefault)
}
}
gwMac, _ := nicList.FindDefaultNicMac()
if gwMac != "" {
err := guest.setDefaultGateway(ctx, userCred, gwMac)
if err != nil {
log.Errorf("setDefaultGateway fail %s", err)
return true, errors.Wrap(err, "setDefaultGateway")
}
}
return true, nil
}
func (guest *SGuest) fixDefaultGateway(ctx context.Context, userCred mcclient.TokenCredential) error {
nics, _ := guest.GetNetworks("")
_, err := guest.fixDefaultGatewayByNics(ctx, userCred, nics)
return err
}
// 挂载网卡
func (self *SGuest) PerformAttachnetwork(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input *api.AttachNetworkInput,
) (*api.SGuest, error) {
if !utils.IsInStringArray(self.Status, []string{api.VM_READY, api.VM_RUNNING}) {
return nil, httperrors.NewBadRequestError("Cannot attach network in status %s", self.Status)
}
// count := len(input.Nets)
if len(input.Nets) == 0 {
if len(input.NetDesc) > 0 {
for _, netDesc := range input.NetDesc {
netConf, err := cmdline.ParseNetworkConfigByJSON(jsonutils.NewString(netDesc), -1)
if err != nil {
return nil, httperrors.NewInputParameterError("failed to parse net_desc %s: %s", netDesc, err)
}
input.Nets = append(input.Nets, netConf)
}
}
if len(input.Nets) == 0 {
return nil, httperrors.NewMissingParameterError("nets/net_desc")
}
}
var inicCnt, enicCnt, isolatedDevCount, defaultGwCnt int
for i := range input.Nets {
err := isValidNetworkInfo(ctx, userCred, input.Nets[i], "", "")
if err != nil {
return nil, err
}
if IsExitNetworkInfo(ctx, userCred, input.Nets[i]) {
enicCnt += 1
// ebw = input.BwLimit
} else {
inicCnt += 1
// ibw = input.BwLimit
}
if input.Nets[i].BwLimit == 0 {
input.Nets[i].BwLimit = options.Options.DefaultBandwidth
}
if input.Nets[i].SriovDevice != nil {
if self.BackupHostId != "" {
return nil, httperrors.NewBadRequestError("Cannot create backup with isolated device")
}
devConfig, err := IsolatedDeviceManager.parseDeviceInfo(userCred, input.Nets[i].SriovDevice)
if err != nil {
return nil, httperrors.NewInputParameterError("parse isolated device description failed %s", err)
}
err = IsolatedDeviceManager.isValidNicDeviceInfo(devConfig)
if err != nil {
return nil, err
}
input.Nets[i].SriovDevice = devConfig
input.Nets[i].Driver = api.NETWORK_DRIVER_VFIO
isolatedDevCount += 1
}
if len(input.Nets[i].Secgroups) > 0 {
secgroupIds, err := isValidSecgroups(ctx, userCred, input.Nets[i].Secgroups)
if err != nil {
return nil, err
}
input.Nets[i].Secgroups = secgroupIds
}
if input.Nets[i].IsDefault {
defaultGwCnt++
}
}
if defaultGwCnt > 1 {
return nil, errors.Wrapf(httperrors.ErrInputParameter, "more than 1 nic(%d) assigned as default gateway", defaultGwCnt)
}
pendingUsage := &SRegionQuota{
Port: inicCnt,
Eport: enicCnt,
//Bw: ibw,
//Ebw: ebw,
}
pendingUsageHost := &SQuota{IsolatedDevice: isolatedDevCount}
keys, err := self.GetRegionalQuotaKeys()
if err != nil {
return nil, err
}
quotakeys, err := self.GetQuotaKeys()
if err != nil {
return nil, err
}
pendingUsageHost.SetKeys(quotakeys)
pendingUsage.SetKeys(keys)
err = quotas.CheckSetPendingQuota(ctx, userCred, pendingUsage)
if err != nil {
return nil, httperrors.NewOutOfQuotaError("%v", err)
}
defer quotas.CancelPendingUsage(ctx, userCred, pendingUsage, pendingUsage, false)
err = quotas.CheckSetPendingQuota(ctx, userCred, pendingUsageHost)
if err != nil {
return nil, httperrors.NewOutOfQuotaError("%v", err)
}
defer quotas.CancelPendingUsage(ctx, userCred, pendingUsageHost, pendingUsageHost, false)
host, _ := self.GetHost()
defer host.ClearSchedDescCache()
var defaultGwGn *SGuestnetwork
for i := range input.Nets {
gns, err := self.attach2NetworkDesc(ctx, userCred, host, input.Nets[i], pendingUsage, nil)
logclient.AddSimpleActionLog(self, logclient.ACT_ATTACH_NETWORK, input.Nets[i], userCred, err == nil)
if err != nil {
quotas.CancelPendingUsage(ctx, userCred, pendingUsage, pendingUsage, false)
return nil, httperrors.NewBadRequestError("%v", err)
}
for gnIdx := range gns {
if gns[gnIdx].IsDefault {
defaultGwGn = &gns[gnIdx]
}
}
if input.Nets[i].SriovDevice != nil {
err = self.allocSriovNicDevice(ctx, userCred, host, &gns[0], input.Nets[i], pendingUsageHost)
if err != nil {
quotas.CancelPendingUsage(ctx, userCred, pendingUsageHost, pendingUsageHost, false)
return nil, errors.Wrap(err, "self.allocSriovNicDevice")
}
}
if len(input.Nets[i].Secgroups) > 0 {
err = self.SaveNetworkSecgroups(ctx, userCred, input.Nets[i].Secgroups, gns[0].Index)
if err != nil {
return nil, errors.Wrap(err, "SaveNetworkSecgroups")
}
}
}
// adjust default gateway
if defaultGwGn != nil {
err := self.setDefaultGateway(ctx, userCred, defaultGwGn.MacAddr)
if err != nil {
log.Errorf("setDefaultGateway fail %s", err)
}
} else {
err := self.fixDefaultGateway(ctx, userCred)
if err != nil {
log.Errorf("fixDefaultGateway fail %s", err)
}
}
if input.DisableSyncConfig != nil && !*input.DisableSyncConfig {
if self.Status == api.VM_READY {
err := self.StartGuestDeployTask(ctx, userCred, nil, "deploy", "")
if err != nil {
return nil, errors.Wrap(err, "StartGuestDeployTask")
}
} else {
err := self.StartSyncTask(ctx, userCred, false, "")
if err != nil {
return nil, errors.Wrap(err, "StartSyncTask")
}
}
}
return nil, nil
}
// 调整带宽
func (guest *SGuest) PerformChangeBandwidth(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input api.ServerChangeBandwidthInput,
) (jsonutils.JSONObject, error) {
if !utils.IsInStringArray(guest.Status, []string{api.VM_READY, api.VM_RUNNING}) {
return nil, httperrors.NewBadRequestError("Cannot change bandwidth in status %s", guest.Status)
}
if input.Bandwidth < 0 && input.TxBwLimit < 0 && input.RxBwLimit < 0 {
return nil, httperrors.NewBadRequestError("Bandwidth, tx_bw_limit and rx_bw_limit must be non-negative")
}
guestnic, err := guest.findGuestnetworkByInfo(input.ServerNetworkInfo)
if err != nil {
return nil, errors.Wrap(err, "findGuestnetworkByInfo")
}
if guestnic.BwLimit != int(input.Bandwidth) || guestnic.TxBwLimit != int(input.TxBwLimit) || guestnic.RxBwLimit != int(input.RxBwLimit) {
oldBw := guestnic.BwLimit
oldTxBw := guestnic.TxBwLimit
oldRxBw := guestnic.RxBwLimit
_, err := db.Update(guestnic, func() error {
guestnic.BwLimit = int(input.Bandwidth)
guestnic.TxBwLimit = int(input.TxBwLimit)
guestnic.RxBwLimit = int(input.RxBwLimit)
return nil
})
if err != nil {
return nil, err
}
eventDesc := guestnic.GetShortDesc(ctx)
eventDesc.Add(jsonutils.NewInt(int64(oldBw)), "old_bw_limit_mbps")
eventDesc.Add(jsonutils.NewInt(int64(oldTxBw)), "old_tx_bw_limit_mbps")
eventDesc.Add(jsonutils.NewInt(int64(oldRxBw)), "old_rx_bw_limit_mbps")
db.OpsLog.LogEvent(guest, db.ACT_CHANGE_BANDWIDTH, eventDesc, userCred)
logclient.AddActionLogWithContext(ctx, guest, logclient.ACT_VM_CHANGE_BANDWIDTH, eventDesc, userCred, true)
if guest.Status == api.VM_READY || (input.NoSync != nil && *input.NoSync) {
// if no sync, just update db
return nil, nil
}
// otherwise, sync configure to host
return nil, guest.StartSyncTask(ctx, userCred, false, "")
}
return nil, nil
}
// 修改源地址检查
func (self *SGuest) PerformModifySrcCheck(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if !utils.IsInStringArray(self.Status, []string{api.VM_READY, api.VM_RUNNING}) {
return nil, httperrors.NewBadRequestError("Cannot change setting in status %s", self.Status)
}
argValue := func(name string, val *bool) error {
if !data.Contains(name) {
return nil
}
b, err := data.Bool(name)
if err != nil {
return errors.Wrapf(err, "fetch arg %s", name)
}
*val = b
return nil
}
var (
srcIpCheck = self.SrcIpCheck.Bool()
srcMacCheck = self.SrcMacCheck.Bool()
)
if err := argValue("src_ip_check", &srcIpCheck); err != nil {
return nil, err
}
if err := argValue("src_mac_check", &srcMacCheck); err != nil {
return nil, err
}
// default: both check on
// switch: mac check off, also implies ip check off
// router: mac check on, ip check off
if !srcMacCheck && srcIpCheck {
srcIpCheck = false
}
if srcIpCheck != self.SrcIpCheck.Bool() || srcMacCheck != self.SrcMacCheck.Bool() {
diff, err := db.Update(self, func() error {
self.SrcIpCheck = tristate.NewFromBool(srcIpCheck)
self.SrcMacCheck = tristate.NewFromBool(srcMacCheck)
return nil
})
if err != nil {
return nil, err
}
db.OpsLog.LogEvent(self, db.ACT_GUEST_SRC_CHECK, diff, userCred)
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_VM_SRC_CHECK, diff, userCred, true)
return nil, self.StartSyncTask(ctx, userCred, false, "")
}
return nil, nil
}
// 调整配置
// 要求虚拟机运行中或关机状态下可以调整配置
func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerChangeConfigInput) (jsonutils.JSONObject, error) {
if !utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_READY}) {
return nil, httperrors.NewInvalidStatusError("Cannot change config in status %s", self.Status)
}
if self.RescueMode {
return nil, httperrors.NewInvalidStatusError("Cannot change config in rescue mode")
}
// 停止计费模式下, 不允许调整配置
if len(self.ExternalId) > 0 && self.ShutdownMode == api.VM_SHUTDOWN_MODE_STOP_CHARGING && self.Status == api.VM_READY {
return nil, httperrors.NewInvalidStatusError("Cannot change config in ready status when shutdown mode is stop charging")
}
driver, err := self.GetDriver()
if err != nil {
return nil, err
}
if !driver.AllowReconfigGuest() {
return nil, httperrors.NewInvalidStatusError("Not allow to change config")
}
if len(self.BackupHostId) > 0 {
return nil, httperrors.NewBadRequestError("guest has backup; changing configuration is not allowed")
}
_, err = self.GetHost()
if err != nil {
return nil, httperrors.NewInvalidStatusError("no valid host")
}
confs, err := driver.ValidateGuestChangeConfigInput(ctx, self, input)
if err != nil {
return nil, errors.Wrap(err, "ValidateGuestChangeConfigInput")
}
if (confs.CpuChanged() || confs.MemChanged() || confs.InstanceTypeChanged()) && self.Status == api.VM_RUNNING {
runningOk, err := driver.IsChangeInstanceTypeWhileRunningSupported(self)
if err != nil {
return nil, err
}
// 仅在不支持开机变配, 或开机降配时需要强制关机,或ARM架构时需要强制关机; force_stop 表示允许关机, 而非一定关机
needForceStop := !runningOk || confs.ConfigReduced() || apis.IsARM(self.OsArch)
if needForceStop && !input.ForceStop {
return nil, httperrors.NewInvalidStatusError("Cannot change config in %s for %s, requires force_stop to change config", self.Status, self.GetHypervisor())
}
if needForceStop {
// 走离线变配路径, 变配完成后自动启动
confs.ForceStop = true
confs.GuestOnline = false
confs.AutoStart = true
}
}
if self.PowerStates == api.VM_POWER_STATES_ON && (confs.CpuChanged() || confs.MemChanged()) && !confs.ForceStop {
confs, err = driver.ValidateGuestHotChangeConfigInput(ctx, self, confs)
if err != nil {
return nil, httperrors.NewInvalidStatusError("cannot change CPU/Memory spec in power status %s: %s", self.PowerStates, err)
}
}
if len(confs.Create) > 0 {
attachStatus, err := driver.GetAttachDiskStatus()
if err != nil {
return nil, httperrors.NewInputParameterError("%v", err)
}
if !utils.IsInStringArray(self.Status, attachStatus) {
return nil, httperrors.NewInvalidStatusError("Cannot attach disk in %s for %s, requires %s", self.Status, self.GetHypervisor(), attachStatus)
}
}
log.Debugf("PerformChangeConfig %s", jsonutils.Marshal(confs).String())
pendingUsage := &SQuota{}
if added := confs.AddedCpu(); added > 0 {
pendingUsage.Cpu = added
}
if added := confs.AddedExtraCpu(); added > 0 {
pendingUsage.Cpu += added
}
if added := confs.AddedMem(); added > 0 {
pendingUsage.Memory = added
}
if added := confs.AddedDisk(); added > 0 {
pendingUsage.Storage = added
}
keys, err := self.GetQuotaKeys()
if err != nil {
return nil, err
}
pendingUsage.SetKeys(keys)
log.Debugf("ChangeConfig pendingUsage %s", jsonutils.Marshal(pendingUsage))
err = quotas.CheckSetPendingQuota(ctx, userCred, pendingUsage)
if err != nil {
return nil, errors.Wrap(err, "CheckSetPendingQuota")
}
// logclient.AddActionLogWithContext(ctx, self, logclient.ACT_CHANGE_CONFIG, confs, userCred, true)
self.StartChangeConfigTask(ctx, userCred, confs, "", pendingUsage)
return nil, nil
}
func (self *SGuest) ChangeConfToSchedDesc(addCpu, addExtraCpu, addMem int, schedInputDisks []*api.DiskConfig) *schedapi.ScheduleInput {
region, _ := self.GetRegion()
devs, _ := self.GetGuestIsolatedDevices()
desc := &schedapi.ScheduleInput{
ServerConfig: schedapi.ServerConfig{
ServerConfigs: &api.ServerConfigs{
Hypervisor: self.Hypervisor,
PreferRegion: region.Id,
PreferHost: self.HostId,
Disks: schedInputDisks,
},
Memory: addMem,
Ncpu: addCpu,
Project: self.ProjectId,
Domain: self.DomainId,
},
OsArch: self.OsArch,
ChangeConfig: true,
HasIsolatedDevice: len(devs) > 0,
ExtraCpuCount: addExtraCpu,
}
return desc
}
func (self *SGuest) StartChangeConfigTask(ctx context.Context, userCred mcclient.TokenCredential,
confs *api.ServerChangeConfigSettings, parentTaskId string, pendingUsage quotas.IQuota) error {
self.SetStatus(ctx, userCred, api.VM_CHANGE_FLAVOR, "")
task, err := taskman.TaskManager.NewTask(ctx, "GuestChangeConfigTask", self, userCred, jsonutils.Marshal(confs).(*jsonutils.JSONDict), parentTaskId, "", pendingUsage)
if err != nil {
return err
}
task.ScheduleRun(nil)
return nil
}
func (self *SGuest) DoPendingDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
eip, _ := self.GetEipOrPublicIp()
if eip != nil {
eip.DoPendingDelete(ctx, userCred)
}
disks, _ := self.GetDisks()
for i := range disks {
if !disks[i].IsDetachable() {
disks[i].DoPendingDelete(ctx, userCred)
} else {
log.Warningf("detachable disk on pending delete guests!!! should be removed earlier")
self.DetachDisk(ctx, &disks[i], userCred)
}
}
SnapshotPolicyResourceManager.RemoveByResource(self.Id, api.SNAPSHOT_POLICY_TYPE_SERVER)
return self.SVirtualResourceBase.DoPendingDelete(ctx, userCred)
}
// 从回收站恢复虚拟机
func (self *SGuest) PerformCancelDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if self.PendingDeleted && !self.Deleted {
err := self.DoCancelPendingDelete(ctx, userCred)
if err != nil {
return nil, errors.Wrap(err, "DoCancelPendingDelete")
}
self.RecoverUsages(ctx, userCred)
}
return nil, nil
}
func (self *SGuest) DoCancelPendingDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
eip, _ := self.GetEipOrPublicIp()
if eip != nil {
eip.DoCancelPendingDelete(ctx, userCred)
}
disks, err := self.GetDisks()
if err != nil {
return err
}
for _, disk := range disks {
disk.DoCancelPendingDelete(ctx, userCred)
}
instanceSnapshots, err := self.GetInstanceSnapshots()
if err != nil {
return err
}
for i := range instanceSnapshots {
instanceSnapshots[i].DoCancelPendingDelete(ctx, userCred)
}
snapshots, err := self.GetDiskSnapshotsNotInInstanceSnapshots(true)
if err != nil {
return err
}
for i := range snapshots {
snapshots[i].DoCancelPendingDelete(ctx, userCred)
}
if self.BillingType == billing_api.BILLING_TYPE_POSTPAID && !self.ExpiredAt.IsZero() {
err := SaveReleaseAt(ctx, self, userCred, time.Time{})
if err != nil {
return err
}
}
err = self.SVirtualResourceBase.DoCancelPendingDelete(ctx, userCred)
if err != nil {
return err
}
notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{
Obj: self,
Action: notifyclient.ActionCreate,
})
return nil
}
func (self *SGuest) StartUndeployGuestTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, targetHostId string) error {
data := jsonutils.NewDict()
if len(targetHostId) > 0 {
data.Add(jsonutils.NewString(targetHostId), "target_host_id")
}
task, err := taskman.TaskManager.NewTask(ctx, "GuestUndeployTask", self, userCred, data, parentTaskId, "", nil)
if err != nil {
return err
}
task.ScheduleRun(nil)
return nil
}
// 重置虚拟机状态
func (self *SGuest) PerformReset(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject,
data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
drv, err := self.GetDriver()
if err != nil {
return nil, err
}
isHard := jsonutils.QueryBoolean(data, "is_hard", false)
if utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_STOP_FAILED, api.VM_KICKSTART_INSTALLING, api.VM_KICKSTART_FAILED, api.VM_KICKSTART_COMPLETED}) {
drv.StartGuestResetTask(self, ctx, userCred, isHard, "")
return nil, nil
}
return nil, httperrors.NewInvalidStatusError("Cannot reset VM in status %s", self.Status)
}
// 同步虚拟机状态
func (self *SGuest) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
var openTask = true
count, err := taskman.TaskManager.QueryTasksOfObject(self, time.Now().Add(-3*time.Minute), &openTask).CountWithError()
if err != nil {
return nil, err
}
if count > 0 {
return nil, httperrors.NewBadRequestError("Guest has %d active tasks and cannot sync status", count)
}
return nil, self.StartSyncstatus(ctx, userCred, "")
}
func (self *SGuest) isNotRunningStatus(status string) bool {
if status == api.VM_READY || status == api.VM_SUSPEND {
return true
}
return false
}
func (self *SGuest) SetStatus(ctx context.Context, userCred mcclient.TokenCredential, status, reason string) error {
if status == api.VM_RUNNING {
if err := self.SetPowerStates(api.VM_POWER_STATES_ON); err != nil {
return errors.Wrap(err, "input status is running")
}
} else if status == api.VM_READY {
if err := self.SetPowerStates(api.VM_POWER_STATES_OFF); err != nil {
return errors.Wrap(err, "input status is ready")
}
}
err := self.SVirtualResourceBase.SetStatus(ctx, userCred, status, reason)
if err != nil {
return errors.Wrap(err, "setStatus")
}
return nil
}
func (self *SGuest) SetPowerStates(powerStates string) error {
if self.PowerStates == powerStates {
return nil
}
_, err := db.Update(self, func() error {
self.PowerStates = powerStates
return nil
})
return errors.Wrapf(err, "Update power states to %s", powerStates)
}
func (self *SGuest) SetBackupGuestStatus(userCred mcclient.TokenCredential, status string, reason string) error {
if self.BackupGuestStatus == status {
return nil
}
oldStatus := self.BackupGuestStatus
_, err := db.Update(self, func() error {
self.BackupGuestStatus = status
return nil
})
if err != nil {
return errors.Wrap(err, "Update backup guest status")
}
if userCred != nil {
notes := fmt.Sprintf("%s=>%s", oldStatus, status)
if len(reason) > 0 {
notes = fmt.Sprintf("%s: %s", notes, reason)
}
db.OpsLog.LogEvent(self, db.ACT_UPDATE_BACKUP_GUEST_STATUS, notes, userCred)
logclient.AddSimpleActionLog(self, logclient.ACT_UPDATE_BACKUP_GUEST_STATUS, notes, userCred, true)
}
return nil
}
func (g *SGuest) SetStatusFromHost(ctx context.Context, userCred mcclient.TokenCredential, resp api.HostUploadGuestStatusInput, hasParentTask bool, originStatus string) error {
statusStr := resp.Status
switch statusStr {
case cloudprovider.CloudVMStatusRunning:
statusStr = api.VM_RUNNING
case cloudprovider.CloudVMStatusSuspend:
statusStr = api.VM_SUSPEND
case cloudprovider.CloudVMStatusStopped, api.VM_READY:
statusStr = api.VM_READY
case api.VM_BLOCK_STREAM, api.VM_BLOCK_STREAM_FAIL:
break
default:
if g.GetHypervisor() != api.HYPERVISOR_POD {
statusStr = api.VM_UNKNOWN
}
}
// Do not override kickstart_installing with running
if statusStr == api.VM_RUNNING && g.Status == api.VM_KICKSTART_INSTALLING {
statusStr = g.Status
log.Infof("guest %s is installing, skip set running status", g.Name)
}
if !hasParentTask {
// migrating status hack
// not change migrating when:
// guest.Status is migrating and task not has parent task
if originStatus == api.VM_MIGRATING && statusStr == api.VM_RUNNING && len(g.ExternalId) == 0 {
statusStr = originStatus
}
}
input := api.ServerPerformStatusInput{
PerformStatusInput: resp.PerformStatusInput,
}
input.Status = statusStr
if _, err := g.PerformStatus(ctx, userCred, nil, input); err != nil {
return errors.Wrapf(err, "perform status of %s", jsonutils.Marshal(resp))
}
return nil
}
func (m *SGuestManager) PerformUploadStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.HostUploadGuestsStatusInput) (*api.GuestUploadStatusesResponse, error) {
out := &api.GuestUploadStatusesResponse{
Guests: make(map[string]*api.GuestUploadStatusResponse),
}
for id, status := range input.Guests {
gst := m.FetchGuestById(id)
if gst == nil {
out.Guests[id] = &api.GuestUploadStatusResponse{
Error: "not found guest",
}
continue
}
resp, err := gst.PerformUploadStatus(ctx, userCred, query, *status)
out.Guests[id] = resp
if err != nil {
resp.Error = err.Error()
continue
}
}
return out, nil
}
func (self *SGuest) PerformUploadStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.HostUploadGuestStatusInput) (*api.GuestUploadStatusResponse, error) {
err := self.SetStatusFromHost(ctx, userCred, input, false, "")
if err != nil {
return nil, errors.Wrap(err, "set status from host")
}
resp := &api.GuestUploadStatusResponse{
OK: true,
Containers: make(map[string]*api.GuestUploadContainerStatusResponse),
}
for cId, cStatus := range input.Containers {
ctr, err := GetContainerManager().FetchById(cId)
if err != nil {
resp.Containers[cId] = &api.GuestUploadContainerStatusResponse{
Error: err.Error(),
}
continue
}
if _, err := ctr.(*SContainer).PerformStatus(ctx, userCred, query, *cStatus); err != nil {
resp.Containers[cId] = &api.GuestUploadContainerStatusResponse{
Error: err.Error(),
}
} else {
resp.Containers[cId] = &api.GuestUploadContainerStatusResponse{
OK: true,
}
}
}
return resp, nil
}
// 同步状态
func (self *SGuest) PerformStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerPerformStatusInput) (jsonutils.JSONObject, error) {
if input.HostId != "" && self.BackupHostId != "" && input.HostId == self.BackupHostId {
// perform status called from slave guest
return nil, self.SetBackupGuestStatus(userCred, input.Status, input.Reason)
}
if input.HostId != "" && input.HostId != self.HostId {
// perform status called from volatile host, eg: migrate dest host
return nil, nil
}
if input.PowerStates != "" {
if err := self.SetPowerStates(input.PowerStates); err != nil {
return nil, errors.Wrap(err, "set power states")
}
}
// Do not override kickstart_installing with running
if input.Status == api.VM_RUNNING && self.Status == api.VM_KICKSTART_INSTALLING {
log.Debugf("guest %s is in kickstart_installing state, skip set running status", self.Name)
return nil, nil
}
preStatus := self.Status
_, err := self.SVirtualResourceBase.PerformStatus(ctx, userCred, query, input.PerformStatusInput)
if err != nil {
return nil, errors.Wrap(err, "SVirtualResourceBase.PerformStatus")
}
if self.HasBackupGuest() {
if input.Status == api.VM_READY {
if err := self.ResetGuestQuorumChildIndex(ctx, userCred); err != nil {
return nil, errors.Wrap(err, "reset guest quorum child index")
}
}
} else if input.Status == api.VM_READY && len(self.GetMetadata(ctx, api.QUORUM_CHILD_INDEX, userCred)) > 0 {
if err := self.ResetGuestQuorumChildIndex(ctx, userCred); err != nil {
return nil, errors.Wrap(err, "reset guest quorum child index")
}
}
if input.Status == api.VM_RUNNING && input.BlockJobsCount == 0 {
if ispId := self.GetMetadata(ctx, api.BASE_INSTANCE_SNAPSHOT_ID, userCred); len(ispId) > 0 {
var disksMerged = true
disks, _ := self.GetDisks()
for _, disk := range disks {
if disk.GetMetadata(ctx, "merge_snapshot", userCred) == "true" {
disksMerged = false
}
}
if disksMerged {
ispM, err := InstanceSnapshotManager.FetchById(ispId)
if err == nil {
isp := ispM.(*SInstanceSnapshot)
isp.DecRefCount(ctx, userCred)
}
self.SetMetadata(ctx, api.BASE_INSTANCE_SNAPSHOT_ID, "", userCred)
}
}
}
if preStatus != self.Status && !self.isNotRunningStatus(preStatus) && self.isNotRunningStatus(self.Status) {
db.OpsLog.LogEvent(self, db.ACT_STOP, "", userCred)
if self.Status == api.VM_READY && !self.DisableDelete.Bool() && self.ShutdownBehavior == api.SHUTDOWN_TERMINATE {
err = self.StartAutoDeleteGuestTask(ctx, userCred, "")
return nil, err
}
}
for cId, cStatus := range input.Containers {
ctr, err := GetContainerManager().FetchById(cId)
if err != nil {
return nil, errors.Wrapf(err, "GetContainerManager(%s)", cId)
}
if _, err := ctr.(*SContainer).PerformStatus(ctx, userCred, query, *cStatus); err != nil {
return nil, errors.Wrapf(err, "PerformStatus(%s) of container", cId)
}
}
return nil, nil
}
// 关机
func (self *SGuest) PerformStop(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject,
input api.ServerStopInput) (jsonutils.JSONObject, error) {
drv, err := self.GetDriver()
if err != nil {
return nil, errors.Wrap(err, "GetDriver")
}
if !input.IsForce {
err := drv.CanStop(self)
if err != nil {
return nil, errors.Wrap(err, "Cannot stop server")
}
}
if err := self.ValidateEncryption(ctx, userCred); err != nil {
return nil, errors.Wrap(httperrors.ErrForbidden, "encryption key not accessible")
}
return nil, self.StartGuestStopTask(ctx, userCred, input.TimeoutSecs, input.IsForce, input.StopCharging, "")
}
// 冻结虚拟机
func (self *SGuest) PerformFreeze(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformFreezeInput) (jsonutils.JSONObject, error) {
if self.Freezed {
return nil, httperrors.NewBadRequestError("virtual resource is already frozen")
}
if utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_STOP_FAILED}) {
return nil, self.StartGuestStopAndFreezeTask(ctx, userCred)
} else {
return self.SVirtualResourceBase.PerformFreeze(ctx, userCred, query, input)
}
}
func (self *SGuest) StartGuestStopAndFreezeTask(ctx context.Context, userCred mcclient.TokenCredential) error {
self.SetStatus(ctx, userCred, api.VM_START_STOP, "")
task, err := taskman.TaskManager.NewTask(ctx, "GuestStopAndFreezeTask", self, userCred, nil, "", "", nil)
if err != nil {
return err
}
task.ScheduleRun(nil)
return nil
}
// 重启
func (self *SGuest) PerformRestart(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerRestartInput) (jsonutils.JSONObject, error) {
if utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_STOP_FAILED, api.VM_KICKSTART_INSTALLING, api.VM_KICKSTART_FAILED, api.VM_KICKSTART_COMPLETED}) || (input.IsForce && self.Status == api.VM_STOPPING) {
driver, err := self.GetDriver()
if err != nil {
return nil, err
}
return nil, driver.StartGuestRestartTask(self, ctx, userCred, input.IsForce, input.TimeoutSecs, "")
}
return nil, httperrors.NewInvalidStatusError("Cannot do restart server in status %s", self.Status)
}
// 发送远程命令
func (self *SGuest) PerformSendkeys(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if self.Hypervisor != api.HYPERVISOR_KVM {
return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.Hypervisor)
}
if self.Status != api.VM_RUNNING {
return nil, httperrors.NewInvalidStatusError("Cannot send keys in status %s", self.Status)
}
keys, err := data.GetString("keys")
if err != nil {
log.Errorln(err)
return nil, httperrors.NewMissingParameterError("keys")
}
err = self.VerifySendKeys(keys)
if err != nil {
return nil, httperrors.NewBadRequestError("%v", err)
}
cmd := fmt.Sprintf("sendkey %s", keys)
duration, err := data.Int("duration")
if err == nil {
cmd = fmt.Sprintf("%s %d", cmd, duration)
}
_, err = self.SendMonitorCommand(ctx, userCred, &api.ServerMonitorInput{COMMAND: cmd})
return nil, err
}
func (self *SGuest) VerifySendKeys(keyStr string) error {
keys := strings.Split(keyStr, "-")
for _, key := range keys {
if !self.IsLegalKey(key) {
return fmt.Errorf("Unknown key '%s'", key)
}
}
return nil
}
func (self *SGuest) IsLegalKey(key string) bool {
singleKeys := "1234567890abcdefghijklmnopqrstuvwxyz"
legalKeys := []string{"ctrl", "ctrl_r", "alt", "alt_r", "shift", "shift_r",
"delete", "esc", "insert", "print", "spc",
"f1", "f2", "f3", "f4", "f5", "f6",
"f7", "f8", "f9", "f10", "f11", "f12",
"home", "pgup", "pgdn", "end",
"up", "down", "left", "right",
"tab", "minus", "equal", "backspace", "backslash",
"bracket_left", "bracket_right", "backslash",
"semicolon", "apostrophe", "grave_accent", "ret",
"comma", "dot", "slash",
"caps_lock", "num_lock", "scroll_lock"}
if len(key) > 1 && !utils.IsInStringArray(key, legalKeys) {
return false
} else if len(key) == 1 && !strings.Contains(singleKeys, key) {
return false
}
return true
}
func (self *SGuest) SendMonitorCommand(ctx context.Context, userCred mcclient.TokenCredential, cmd *api.ServerMonitorInput) (jsonutils.JSONObject, error) {
host, _ := self.GetHost()
url := fmt.Sprintf("%s/servers/%s/monitor", host.ManagerUri, self.Id)
header := http.Header{}
header.Add("X-Auth-Token", userCred.GetTokenString())
body := jsonutils.NewDict()
body.Add(jsonutils.NewString(cmd.COMMAND), "cmd")
body.Add(jsonutils.NewBool(cmd.QMP), "qmp")
_, res, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, body, false)
if err != nil {
return nil, err
}
ret := res.(*jsonutils.JSONDict)
return ret, nil
}
// 绑定EIP
func (self *SGuest) PerformAssociateEip(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerAssociateEipInput) (jsonutils.JSONObject, error) {
err := self.IsEipAssociable()
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
eipStr := input.EipId
if len(eipStr) == 0 {
return nil, httperrors.NewMissingParameterError("eip_id")
}
eipObj, err := ElasticipManager.FetchByIdOrName(ctx, userCred, eipStr)
if err != nil {
if err == sql.ErrNoRows {
return nil, httperrors.NewResourceNotFoundError("eip %s not found", eipStr)
} else {
return nil, httperrors.NewGeneralError(err)
}
}
eip := eipObj.(*SElasticip)
eipRegion, err := eip.GetRegion()
if err != nil {
return nil, httperrors.NewGeneralError(errors.Wrapf(err, "eip.GetRegion"))
}
instRegion, _ := self.getRegion()
if eip.Mode == api.EIP_MODE_INSTANCE_PUBLICIP {
return nil, httperrors.NewUnsupportOperationError("fixed eip cannot be associated")
}
if eip.IsAssociated() {
return nil, httperrors.NewConflictError("eip has been associated")
}
if eipRegion.Id != instRegion.Id {
return nil, httperrors.NewInputParameterError("cannot associate EIP and instance in different regions")
}
if len(eip.NetworkId) > 0 {
gns, err := self.GetNetworks("")
if err != nil {
return nil, httperrors.NewGeneralError(errors.Wrap(err, "GetNetworks"))
}
for _, gn := range gns {
if gn.NetworkId == eip.NetworkId {
return nil, httperrors.NewInputParameterError("cannot associate EIP with the same network")
}
}
}
eipZone, _ := eip.GetZone()
if eipZone != nil {
insZone, _ := self.getZone()
if eipZone.Id != insZone.Id {
return nil, httperrors.NewInputParameterError("cannot associate EIP and instance in different zones")
}
}
host, _ := self.GetHost()
if host == nil {
return nil, httperrors.NewInputParameterError("server host not found")
}
if host.ManagerId != eip.ManagerId {
return nil, httperrors.NewInputParameterError("cannot associate EIP and instance with different providers")
}
self.SetStatus(ctx, userCred, api.INSTANCE_ASSOCIATE_EIP, "associate eip")
params := jsonutils.NewDict()
params.Add(jsonutils.NewString(self.ExternalId), "instance_external_id")
params.Add(jsonutils.NewString(self.Id), "instance_id")
params.Add(jsonutils.NewString(api.EIP_ASSOCIATE_TYPE_SERVER), "instance_type")
if len(input.IpAddr) > 0 {
params.Add(jsonutils.NewString(input.IpAddr), "ip_addr")
}
err = eip.StartEipAssociateTask(ctx, userCred, params, "")
return nil, err
}
// 解绑EIP
func (self *SGuest) PerformDissociateEip(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerDissociateEipInput) (jsonutils.JSONObject, error) {
eip, err := self.GetElasticIp()
if err != nil {
log.Errorf("Fail to get Eip %s", err)
return nil, httperrors.NewGeneralError(err)
}
if eip == nil {
return nil, httperrors.NewInvalidStatusError("No eip to dissociate")
}
err = db.IsObjectRbacAllowed(ctx, eip, userCred, policy.PolicyActionGet)
if err != nil {
return nil, errors.Wrap(err, "eip is not accessible")
}
self.SetStatus(ctx, userCred, api.INSTANCE_DISSOCIATE_EIP, "associate eip")
autoDelete := (input.AudoDelete != nil && *input.AudoDelete)
err = eip.StartEipDissociateTask(ctx, userCred, autoDelete, "")
if err != nil {
log.Errorf("fail to start dissociate task %s", err)
return nil, httperrors.NewGeneralError(err)
}
return nil, nil
}
// 创建EIP
func (self *SGuest) PerformCreateEip(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerCreateEipInput) (jsonutils.JSONObject, error) {
var (
host, _ = self.GetHost()
region, _ = host.GetRegion()
regionDriver = region.GetDriver()
bw = input.Bandwidth
chargeType = input.ChargeType
bgpType = input.BgpType
autoDellocate = (input.AutoDellocate != nil && *input.AutoDellocate)
)
err := self.IsEipAssociable()
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
if len(chargeType) == 0 {
chargeType = billing_api.TNetChargeType(regionDriver.GetEipDefaultChargeType())
}
if chargeType == billing_api.NET_CHARGE_TYPE_BY_BANDWIDTH {
if bw == 0 {
return nil, httperrors.NewMissingParameterError("bandwidth")
}
}
driver, err := self.GetDriver()
if err != nil {
return nil, err
}
err = driver.ValidateCreateEip(ctx, userCred, input)
if err != nil {
return nil, err
}
eipPendingUsage := &SRegionQuota{Eip: 1}
keys, err := self.GetRegionalQuotaKeys()
if err != nil {
return nil, err
}
eipPendingUsage.SetKeys(keys)
err = quotas.CheckSetPendingQuota(ctx, userCred, eipPendingUsage)
if err != nil {
return nil, httperrors.NewOutOfQuotaError("Out of eip quota: %s", err)
}
eip, err := ElasticipManager.NewEipForVMOnHost(ctx, userCred, &NewEipForVMOnHostArgs{
Bandwidth: int(bw),
BgpType: bgpType,
ChargeType: chargeType,
AutoDellocate: autoDellocate,
Guest: self,
Host: host,
PendingUsage: eipPendingUsage,
})
if err != nil {
quotas.CancelPendingUsage(ctx, userCred, eipPendingUsage, eipPendingUsage, false)
return nil, httperrors.NewGeneralError(err)
}
opts := api.ElasticipAssociateInput{
InstanceId: self.Id,
InstanceExternalId: self.ExternalId,
InstanceType: api.EIP_ASSOCIATE_TYPE_SERVER,
}
err = eip.AllocateAndAssociateInstance(ctx, userCred, self, opts, "")
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
return nil, nil
}
func (self *SGuest) setUserData(ctx context.Context, userCred mcclient.TokenCredential, data string) error {
if err := userdata.ValidateUserdata(data, self.OsType); err != nil {
return err
}
encodeData, err := userdata.Encode(data)
if err != nil {
return errors.Wrap(err, "encode guest userdata")
}
err = self.SetMetadata(ctx, "user_data", encodeData, userCred)
if err != nil {
return err
}
return nil
}
func (self *SGuest) GetUserData(ctx context.Context, userCred mcclient.TokenCredential) string {
userData := self.GetMetadata(ctx, "user_data", userCred)
if len(userData) == 0 {
return userData
}
decodeData, _ := userdata.Decode(userData)
return decodeData
}
// 更新自定义脚本数据
func (self *SGuest) PerformUserData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerUserDataInput) (jsonutils.JSONObject, error) {
if len(input.UserData) == 0 {
return nil, httperrors.NewMissingParameterError("user_data")
}
// validate UserData
if err := userdata.ValidateUserdata(input.UserData, self.OsType); err != nil {
return nil, httperrors.NewInputParameterError("Invalid userdata: %v", err)
}
err := self.setUserData(ctx, userCred, input.UserData)
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
if len(self.HostId) > 0 {
return nil, self.StartSyncTask(ctx, userCred, false, "")
}
return nil, nil
}
// 设置Qemu参数
func (self *SGuest) PerformSetQemuParams(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
isaSerial, err := data.GetString("disable_isa_serial")
if err == nil {
err = self.SetMetadata(ctx, "disable_isa_serial", isaSerial, userCred)
if err != nil {
return nil, err
}
}
pvpanic, err := data.GetString("disable_pvpanic")
if err == nil {
err = self.SetMetadata(ctx, "disable_pvpanic", pvpanic, userCred)
if err != nil {
return nil, err
}
}
usbKbd, err := data.GetString("disable_usb_kbd")
if err == nil {
err = self.SetMetadata(ctx, "disable_usb_kbd", usbKbd, userCred)
if err != nil {
return nil, err
}
}
usbContType, err := data.GetString("usb_controller_type")
if err == nil {
err = self.SetMetadata(ctx, "usb_controller_type", usbContType, userCred)
if err != nil {
return nil, err
}
}
return nil, nil
}
func (self *SGuest) SwitchToBackup(userCred mcclient.TokenCredential) error {
diff, err := db.Update(self, func() error {
self.HostId, self.BackupHostId = self.BackupHostId, self.HostId
return nil
})
if err != nil {
return err
}
db.OpsLog.LogEvent(self, db.ACT_UPDATE, diff, userCred)
return nil
}
// 主备机切换
func (self *SGuest) PerformSwitchToBackup(
ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
if self.Status == api.VM_BLOCK_STREAM {
return nil, httperrors.NewBadRequestError("Cannot swith to backup when guest in status %s", self.Status)
}
if len(self.BackupHostId) == 0 {
return nil, httperrors.NewBadRequestError("Guest no backup host")
}
backupHost := HostManager.FetchHostById(self.BackupHostId)
if backupHost.HostStatus != api.HOST_ONLINE {
return nil, httperrors.NewBadRequestError("Can't switch to backup host on host status %s", backupHost.HostStatus)
}
if !self.IsGuestBackupMirrorJobReady(ctx, userCred) {
return nil, httperrors.NewBadRequestError("Guest can't switch to backup, mirror job not ready")
}
if !utils.IsInStringArray(self.BackupGuestStatus, []string{api.VM_RUNNING, api.VM_READY, api.VM_UNKNOWN}) {
return nil, httperrors.NewInvalidStatusError("Guest can't switch to backup with backup status %s", self.BackupGuestStatus)
}
oldStatus := self.Status
taskData := jsonutils.NewDict()
taskData.Set("old_status", jsonutils.NewString(oldStatus))
taskData.Set("auto_start", jsonutils.NewBool(jsonutils.QueryBoolean(data, "auto_start", false)))
if task, err := taskman.TaskManager.NewTask(ctx, "GuestSwitchToBackupTask", self, userCred, taskData, "", "", nil); err != nil {
log.Errorln(err)
return nil, err
} else {
self.SetStatus(ctx, userCred, api.VM_SWITCH_TO_BACKUP, "Switch to backup")
task.ScheduleRun(nil)
}
return nil, nil
}
func (manager *SGuestManager) getGuests(ctx context.Context, userCred mcclient.TokenCredential, data jsonutils.JSONObject) ([]SGuest, error) {
_guests := []string{}
data.Unmarshal(&_guests, "guests")
if len(_guests) == 0 {
return nil, httperrors.NewMissingParameterError("guests")
}
guests := []SGuest{}
q1 := manager.Query().In("id", _guests)
q2 := manager.Query().In("name", _guests)
q2 = manager.FilterByOwner(ctx, q2, manager, userCred, userCred, manager.NamespaceScope())
q2 = manager.FilterBySystemAttributes(q2, userCred, data, manager.ResourceScope())
q := sqlchemy.Union(q1, q2).Query().Distinct()
err := db.FetchModelObjects(manager, q, &guests)
if err != nil {
return nil, err
}
guestStr := []string{}
for _, guest := range guests {
guestStr = append(guestStr, guest.Id)
guestStr = append(guestStr, guest.Name)
}
for _, guest := range _guests {
if !utils.IsInStringArray(guest, guestStr) {
return nil, httperrors.NewResourceNotFoundError("failed to found guest %s", guest)
}
}
return guests, nil
}
func (manager *SGuestManager) getUserMetadata(data jsonutils.JSONObject) (map[string]string, error) {
if !data.Contains("metadata") {
return nil, httperrors.NewMissingParameterError("metadata")
}
metadata, err := data.GetMap("metadata")
if err != nil {
return nil, httperrors.NewInputParameterError("input data not key value dict")
}
dictStore := make(map[string]string)
for k, v := range metadata {
dictStore[db.USER_TAG_PREFIX+k], _ = v.GetString()
}
return dictStore, nil
}
func (manager *SGuestManager) PerformBatchUserMetadata(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
guests, err := manager.getGuests(ctx, userCred, data)
if err != nil {
return nil, err
}
metadata, err := manager.getUserMetadata(data)
if err != nil {
return nil, err
}
for _, guest := range guests {
err := guest.SetUserMetadataValues(ctx, metadata, userCred)
if err != nil {
msg := fmt.Errorf("set guest %s(%s) user-metadata error: %v", guest.Name, guest.Id, err)
return nil, httperrors.NewGeneralError(msg)
}
}
return jsonutils.Marshal(guests), nil
}
func (manager *SGuestManager) PerformBatchSetUserMetadata(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
guests, err := manager.getGuests(ctx, userCred, data)
if err != nil {
return nil, err
}
metadata, err := manager.getUserMetadata(data)
if err != nil {
return nil, err
}
for _, guest := range guests {
err := guest.SetUserMetadataAll(ctx, metadata, userCred)
if err != nil {
msg := fmt.Errorf("set guest %s(%s) user-metadata error: %v", guest.Name, guest.Id, err)
return nil, httperrors.NewGeneralError(msg)
}
}
return jsonutils.Marshal(guests), nil
}
func (self *SGuest) PerformBlockStreamFailed(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if len(self.BackupHostId) > 0 {
if err := self.SetGuestBackupMirrorJobFailed(ctx, userCred); err != nil {
return nil, errors.Wrap(err, "set guest backup mirror job failed")
}
}
if self.Status == api.VM_BLOCK_STREAM || self.Status == api.VM_RUNNING {
reason, _ := data.GetString("reason")
logclient.AddSimpleActionLog(self, logclient.ACT_VM_BLOCK_STREAM, reason, userCred, false)
return nil, self.SetStatus(ctx, userCred, api.VM_BLOCK_STREAM_FAIL, reason)
}
return nil, nil
}
func (self *SGuest) PerformSlaveBlockStreamReady(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if len(self.BackupHostId) > 0 {
if err := self.TrySetGuestBackupMirrorJobReady(ctx, userCred); err != nil {
return nil, errors.Wrap(err, "set guest backup mirror job status ready")
}
self.SetBackupGuestStatus(userCred, api.VM_RUNNING, "perform slave block stream ready")
}
return nil, nil
}
func (self *SGuest) PerformBlockMirrorReady(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if self.Status == api.VM_BLOCK_STREAM || self.Status == api.VM_RUNNING {
diskId, err := data.GetString("disk_id")
if err != nil {
return nil, httperrors.NewMissingParameterError("disk_id")
}
log.Infof("disk_id %s", diskId)
disk := DiskManager.FetchDiskById(diskId)
if disk == nil {
return nil, httperrors.NewNotFoundError("disk %s not found", diskId)
}
if taskId := disk.GetMetadata(ctx, api.DISK_CLONE_TASK_ID, userCred); len(taskId) > 0 {
log.Infof("task_id %s", taskId)
if err := self.startSwitchToClonedDisk(ctx, userCred, taskId); err != nil {
return nil, errors.Wrap(err, "startSwitchToClonedDisk")
}
}
}
return nil, nil
}
func (self *SGuest) StartMirrorJob(ctx context.Context, userCred mcclient.TokenCredential, nbdServerPort int64, parentTaskId string) error {
taskData := jsonutils.NewDict()
taskData.Set("nbd_server_port", jsonutils.NewInt(nbdServerPort))
if task, err := taskman.TaskManager.NewTask(
ctx, "GuestReSyncToBackup", self, userCred, taskData, parentTaskId, "", nil); err != nil {
log.Errorln(err)
return err
} else {
task.ScheduleRun(nil)
}
return nil
}
func (manager *SGuestManager) PerformDirtyServerStart(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
guestId, err := data.GetString("guest_id")
if err != nil {
return nil, httperrors.NewMissingParameterError("guest_id")
}
guest := manager.FetchGuestById(guestId)
if guest == nil {
return nil, httperrors.NewNotFoundError("Guest %s not found", guestId)
}
hostId, _ := data.GetString("host_id")
if len(hostId) == 0 {
return nil, httperrors.NewMissingParameterError("host_id")
}
if guest.HostId == hostId {
// master guest
err := guest.StartGueststartTask(ctx, userCred, nil, "")
return nil, err
} else if guest.BackupHostId == hostId {
// slave guest
err := guest.GuestStartAndSyncToBackup(ctx, userCred, "", guest.Status)
return nil, err
}
return nil, nil
}
func (manager *SGuestManager) PerformDirtyServerVerify(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
guestId, err := data.GetString("guest_id")
if err != nil {
return nil, httperrors.NewMissingParameterError("guest_id")
}
iguest, err := manager.FetchById(guestId)
if err == sql.ErrNoRows {
ret := jsonutils.NewDict()
ret.Set("guest_unknown_need_clean", jsonutils.JSONTrue)
return ret, nil
} else {
if err != nil {
return nil, httperrors.NewInternalServerError("fetch guest failed %s", err)
} else {
guest := iguest.(*SGuest)
hostId, _ := data.GetString("host_id")
if len(hostId) == 0 {
return nil, httperrors.NewMissingParameterError("host_id")
}
if guest.HostId != hostId && guest.BackupHostId != hostId {
return nil, guest.StartGuestDeleteOnHostTask(ctx, userCred, hostId, false, "")
}
}
}
return nil, nil
}
func (self *SGuest) StartGuestDeleteOnHostTask(ctx context.Context, userCred mcclient.TokenCredential, hostId string, purge bool, parentTaskId string) error {
taskData := jsonutils.NewDict()
taskData.Set("host_id", jsonutils.NewString(hostId))
taskData.Set("purge", jsonutils.NewBool(purge))
if task, err := taskman.TaskManager.NewTask(
ctx, "GuestDeleteOnHostTask", self, userCred, taskData, parentTaskId, "", nil); err != nil {
log.Errorln(err)
return err
} else {
task.ScheduleRun(nil)
}
return nil
}
func (guest *SGuest) GuestStartAndSyncToBackup(
ctx context.Context, userCred mcclient.TokenCredential, parentTaskId, guestStatus string,
) error {
data := jsonutils.NewDict()
data.Set("guest_status", jsonutils.NewString(guestStatus))
task, err := taskman.TaskManager.NewTask(
ctx, "GuestStartAndSyncToBackupTask", guest, userCred, data, parentTaskId, "", nil)
if err != nil {
log.Errorln(err)
return err
} else {
task.ScheduleRun(nil)
}
return nil
}
func (self *SGuest) guestDisksStorageTypeIsLocal() bool {
disks, _ := self.GetDisks()
for _, disk := range disks {
storage, _ := disk.GetStorage()
if storage.StorageType != api.STORAGE_LOCAL && storage.StorageType != api.STORAGE_LVM {
return false
}
}
return true
}
func (self *SGuest) guestDisksStorageTypeIsShared() bool {
disks, _ := self.GetDisks()
for _, disk := range disks {
storage, _ := disk.GetStorage()
if storage.StorageType == api.STORAGE_LOCAL || storage.StorageType == api.STORAGE_LVM {
return false
}
}
return true
}
// 创建备机
func (self *SGuest) PerformCreateBackup(
ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
if len(self.BackupHostId) > 0 {
return nil, httperrors.NewBadRequestError("Already have backup server")
}
if self.Status != api.VM_READY {
return nil, httperrors.NewBadRequestError("Can't create backup in guest status %s", self.Status)
}
if !self.guestDisksStorageTypeIsLocal() {
return nil, httperrors.NewBadRequestError("Cannot create backup with shared storage")
}
if self.Hypervisor != api.HYPERVISOR_KVM {
return nil, httperrors.NewBadRequestError("Backup only support hypervisor kvm")
}
devs, _ := self.GetGuestIsolatedDevices()
if len(devs) > 0 {
return nil, httperrors.NewBadRequestError("Cannot create backup with isolated devices")
}
hasSnapshot, err := self.GuestDisksHasSnapshot()
if err != nil {
return nil, httperrors.NewInternalServerError("GuestDisksHasSnapshot failed %s", err)
}
if hasSnapshot {
return nil, httperrors.NewBadRequestError("Cannot create backup with snapshot")
}
return self.StartGuestCreateBackupTask(ctx, userCred, "", data)
}
func (self *SGuest) StartGuestCreateBackupTask(
ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
req := self.getGuestBackupResourceRequirements(ctx, userCred)
keys, err := self.GetQuotaKeys()
if err != nil {
return nil, err
}
req.SetKeys(keys)
err = quotas.CheckSetPendingQuota(ctx, userCred, &req)
if err != nil {
return nil, httperrors.NewOutOfQuotaError("%v", err)
}
params := data.(*jsonutils.JSONDict)
params.Set("guest_status", jsonutils.NewString(self.Status))
self.SetStatus(ctx, userCred, api.VM_BACKUP_CREATING, "")
task, err := taskman.TaskManager.NewTask(ctx, "GuestCreateBackupTask", self, userCred, params, parentTaskId, "", &req)
if err != nil {
quotas.CancelPendingUsage(ctx, userCred, &req, &req, false)
log.Errorln(err)
return nil, err
} else {
task.ScheduleRun(nil)
}
return nil, nil
}
// 删除备机
func (self *SGuest) PerformDeleteBackup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if len(self.BackupHostId) == 0 {
return nil, httperrors.NewBadRequestError("Guest without backup")
}
backupHost := HostManager.FetchHostById(self.BackupHostId)
if backupHost == nil {
return nil, httperrors.NewNotFoundError("Guest backup host not found")
}
if backupHost.Status == api.HOST_OFFLINE && !jsonutils.QueryBoolean(data, "purge", false) {
return nil, httperrors.NewBadRequestError("Backup host is offline")
}
taskData := jsonutils.NewDict()
taskData.Set("purge", jsonutils.NewBool(jsonutils.QueryBoolean(data, "purge", false)))
taskData.Set("create", jsonutils.NewBool(jsonutils.QueryBoolean(data, "create", false)))
self.SetStatus(ctx, userCred, api.VM_DELETING_BACKUP, "delete backup server")
if task, err := taskman.TaskManager.NewTask(
ctx, "GuestDeleteBackupTask", self, userCred, taskData, "", "", nil); err != nil {
log.Errorln(err)
return nil, err
} else {
task.ScheduleRun(nil)
}
return nil, nil
}
func (self *SGuest) CreateBackupDisks(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
if task, err := taskman.TaskManager.NewTask(ctx, "GuestCreateBackupDisksTask", self, userCred, nil, parentTaskId, "", nil); err != nil {
log.Errorln(err)
return err
} else {
task.ScheduleRun(nil)
}
return nil
}
func (self *SGuest) StartCreateBackup(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, kwargs *jsonutils.JSONDict) error {
if kwargs == nil {
kwargs = jsonutils.NewDict()
}
kwargs.Add(jsonutils.NewString("create"), "deploy_action")
if task, err := taskman.TaskManager.NewTask(ctx, "GuestDeployBackupTask", self, userCred, kwargs, parentTaskId, "", nil); err != nil {
log.Errorln(err)
return err
} else {
task.ScheduleRun(nil)
}
return nil
}
// 备机开机
func (self *SGuest) PerformStartBackup(
ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
if !self.HasBackupGuest() {
return nil, httperrors.NewBadRequestError("guest has no backup guest")
}
if host := HostManager.FetchHostById(self.BackupHostId); host.HostStatus != api.HOST_ONLINE {
return nil, httperrors.NewBadRequestError("can't start backup guest on host status %s", host.HostStatus)
}
if self.Status != api.VM_RUNNING || self.BackupGuestStatus == api.VM_RUNNING {
return nil, httperrors.NewBadRequestError("can't start backup guest on backup guest status %s", self.BackupGuestStatus)
}
return nil, self.GuestStartAndSyncToBackup(ctx, userCred, "", self.Status)
}
func (self *SGuest) PerformSetExtraOption(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerSetExtraOptionInput) (jsonutils.JSONObject, error) {
err := input.Validate()
if err != nil {
return nil, errors.Wrap(err, "input.Validate")
}
extraOptions := self.GetExtraOptions(ctx, userCred)
optVal := make([]string, 0)
extraOptions.Unmarshal(&optVal, input.Key)
if !utils.IsInStringArray(input.Value, optVal) {
optVal = append(optVal, input.Value)
}
extraOptions.Set(input.Key, jsonutils.Marshal(optVal))
return nil, self.SetExtraOptions(ctx, userCred, extraOptions)
}
func (self *SGuest) GetExtraOptions(ctx context.Context, userCred mcclient.TokenCredential) *jsonutils.JSONDict {
options := self.GetMetadataJson(ctx, "extra_options", userCred)
o, ok := options.(*jsonutils.JSONDict)
if ok {
return o
}
return jsonutils.NewDict()
}
func (self *SGuest) SetExtraOptions(ctx context.Context, userCred mcclient.TokenCredential, extraOptions *jsonutils.JSONDict) error {
return self.SetMetadata(ctx, "extra_options", extraOptions, userCred)
}
func (self *SGuest) PerformDelExtraOption(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerDelExtraOptionInput) (jsonutils.JSONObject, error) {
err := input.Validate()
if err != nil {
return nil, errors.Wrap(err, "input.Validate")
}
extraOptions := self.GetExtraOptions(ctx, userCred)
var newOpt []string
if len(input.Value) > 0 {
optVal := make([]string, 0)
extraOptions.Unmarshal(&optVal, input.Key)
for _, v := range optVal {
if v != input.Value {
newOpt = append(newOpt, v)
}
}
}
if len(newOpt) > 0 {
extraOptions.Set(input.Key, jsonutils.Marshal(newOpt))
} else if extraOptions.Contains(input.Key) {
extraOptions.Remove(input.Key)
}
return nil, self.SetExtraOptions(ctx, userCred, extraOptions)
}
// 取消定时删除
func (self *SGuest) PerformCancelExpire(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if self.BillingType != billing_api.BILLING_TYPE_POSTPAID {
return nil, httperrors.NewBadRequestError("guest billing type %s does not support canceling expiration", self.BillingType)
}
err := SaveReleaseAt(ctx, self, userCred, time.Time{})
if err != nil {
return nil, err
}
disks, err := self.GetDisks()
if err != nil {
return nil, err
}
for i := 0; i < len(disks); i += 1 {
if disks[i].BillingType == billing_api.BILLING_TYPE_POSTPAID {
err := SaveReleaseAt(ctx, &disks[i], userCred, time.Time{})
if err != nil {
return nil, err
}
}
}
return nil, nil
}
// 设置定时删除
func (self *SGuest) PerformPostpaidExpire(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PostpaidExpireInput) (jsonutils.JSONObject, error) {
if self.BillingType != billing_api.BILLING_TYPE_POSTPAID {
return nil, httperrors.NewBadRequestError("guest billing type is %s", self.BillingType)
}
driver, err := self.GetDriver()
if err != nil {
return nil, err
}
if !driver.IsSupportPostpaidExpire() {
return nil, httperrors.NewBadRequestError("guest hypervisor %s does not support postpaid expiration", self.Hypervisor)
}
releaseAt, err := input.GetReleaseAt()
if err != nil {
return nil, err
}
err = SaveReleaseAt(ctx, self, userCred, releaseAt)
if err != nil {
return nil, err
}
return nil, nil
}
// 续费
func (self *SGuest) PerformRenew(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.RenewInput) (jsonutils.JSONObject, error) {
if self.BillingType != billing_api.BILLING_TYPE_PREPAID {
return nil, httperrors.NewUnsupportOperationError("Only %s guest support renew operation", billing_api.BILLING_TYPE_PREPAID)
}
if len(input.Duration) == 0 {
return nil, httperrors.NewMissingParameterError("duration")
}
bc, err := billing.ParseBillingCycle(input.Duration)
if err != nil {
return nil, httperrors.NewInputParameterError("invalid duration %s: %s", input.Duration, err)
}
driver, err := self.GetDriver()
if err != nil {
return nil, err
}
if !driver.IsSupportedBillingCycle(bc) {
return nil, httperrors.NewInputParameterError("unsupported duration %s", input.Duration)
}
err = self.startGuestRenewTask(ctx, userCred, input.Duration, "")
if err != nil {
return nil, err
}
return nil, nil
}
func (self *SGuest) GetStorages() ([]SStorage, error) {
q := StorageManager.Query().Distinct()
disks := DiskManager.Query().SubQuery()
guestdisks := GuestdiskManager.Query().Equals("guest_id", self.Id).SubQuery()
q = q.Join(disks, sqlchemy.Equals(disks.Field("storage_id"), q.Field("id")))
q = q.Join(guestdisks, sqlchemy.Equals(guestdisks.Field("disk_id"), disks.Field("id")))
ret := []SStorage{}
err := db.FetchModelObjects(StorageManager, q, &ret)
if err != nil {
return nil, err
}
return ret, nil
}
func (self *SGuest) startGuestRenewTask(ctx context.Context, userCred mcclient.TokenCredential, duration string, parentTaskId string) error {
self.SetStatus(ctx, userCred, api.VM_RENEWING, "")
data := jsonutils.NewDict()
data.Add(jsonutils.NewString(duration), "duration")
task, err := taskman.TaskManager.NewTask(ctx, "GuestRenewTask", self, userCred, data, parentTaskId, "", nil)
if err != nil {
log.Errorf("fail to crate GuestRenewTask %s", err)
return err
}
task.ScheduleRun(nil)
return nil
}
func (self *SGuest) SaveRenewInfo(
ctx context.Context, userCred mcclient.TokenCredential,
bc *billing.SBillingCycle, expireAt *time.Time, billingType billing_api.TBillingType,
) error {
err := SaveRenewInfo(ctx, userCred, self, bc, expireAt, billingType)
if err != nil {
return err
}
disks, err := self.GetDisks()
if err != nil {
return err
}
for i := 0; i < len(disks); i += 1 {
if disks[i].AutoDelete {
err = SaveRenewInfo(ctx, userCred, &disks[i], bc, expireAt, billingType)
if err != nil {
return err
}
}
}
return nil
}
func (self *SGuest) PerformSetNetworkNumQueues(
ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerSetNetworkNumQueuesInput,
) (jsonutils.JSONObject, error) {
if self.Status != api.VM_READY {
return nil, httperrors.NewInvalidStatusError("can't set network num_queues on vm %s", self.Status)
}
if input.NumQueues < 1 {
return nil, httperrors.NewInputParameterError("invalid num_queues %d", input.NumQueues)
}
gn, err := self.GetGuestnetworkByMac(input.MacAddr)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, httperrors.NewNotFoundError("guest network mac %s not found", input.MacAddr)
}
}
_, err = db.Update(gn, func() error {
gn.NumQueues = input.NumQueues
return nil
})
return nil, err
}
func (self *SGuest) PerformStreamDisksComplete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
disks, err := self.GetDisks()
if err != nil {
return nil, err
}
for _, disk := range disks {
if len(disk.SnapshotId) > 0 && disk.GetMetadata(ctx, "merge_snapshot", userCred) == "true" {
SnapshotManager.AddRefCount(disk.SnapshotId, -1)
disk.SetMetadata(ctx, "merge_snapshot", jsonutils.JSONFalse, userCred)
}
if len(disk.GetMetadata(ctx, api.DISK_META_REMOTE_ACCESS_PATH, nil)) > 0 {
disk.SetMetadata(ctx, api.DISK_META_REMOTE_ACCESS_PATH, "", userCred)
}
}
return nil, nil
}
// 导入虚拟机
func (man *SGuestManager) PerformImport(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
desc := &api.SImportGuestDesc{}
if err := data.Unmarshal(desc); err != nil {
return nil, httperrors.NewInputParameterError("Invalid desc: %s", data.String())
}
if len(desc.Id) == 0 {
return nil, httperrors.NewInputParameterError("Server Id is empty")
}
if len(desc.Name) == 0 {
return nil, httperrors.NewInputParameterError("Server Name is empty")
}
if obj, _ := man.FetchByIdOrName(ctx, userCred, desc.Id); obj != nil {
return nil, httperrors.NewInputParameterError("Server %s already exists", desc.Id)
}
if err := db.NewNameValidator(ctx, man, userCred, desc.Name, nil); err != nil {
return nil, err
}
if hostObj, _ := HostManager.FetchByIdOrName(ctx, userCred, desc.HostId); hostObj == nil {
return nil, httperrors.NewNotFoundError("Host %s not found", desc.HostId)
} else {
desc.HostId = hostObj.GetId()
}
guset, err := man.DoImport(ctx, userCred, desc)
if err != nil {
return nil, err
}
return jsonutils.Marshal(guset), nil
}
func (man *SGuestManager) DoImport(
ctx context.Context, userCred mcclient.TokenCredential, desc *api.SImportGuestDesc,
) (*SGuest, error) {
// 1. create import guest on host
gst, err := man.createImportGuest(ctx, userCred, desc)
if err != nil {
return nil, err
}
// 2. import networks
if err := gst.importNics(ctx, userCred, desc.Nics); err != nil {
return gst, err
}
// 3. import disks
if err := gst.importDisks(ctx, userCred, desc.Disks); err != nil {
return gst, err
}
// 4. set metadata
for k, v := range desc.Metadata {
gst.SetMetadata(ctx, k, v, userCred)
}
return gst, nil
}
func (man *SGuestManager) createImportGuest(ctx context.Context, userCred mcclient.TokenCredential, desc *api.SImportGuestDesc) (*SGuest, error) {
model, err := db.NewModelObject(man)
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
gst, ok := model.(*SGuest)
if !ok {
return nil, httperrors.NewGeneralError(fmt.Errorf("Can't convert %#v to *SGuest model", model))
}
gst.ProjectId = userCred.GetProjectId()
gst.DomainId = userCred.GetProjectDomainId()
gst.IsSystem = desc.IsSystem
gst.Id = desc.Id
gst.Name = desc.Name
gst.HostId = desc.HostId
gst.Status = api.VM_IMPORT
gst.Hypervisor = desc.Hypervisor
gst.VmemSize = desc.MemSizeMb
gst.VcpuCount = desc.Cpu
gst.BootOrder = desc.BootOrder
gst.Description = desc.Description
err = man.TableSpec().Insert(ctx, gst)
return gst, err
}
func ToNetConfig(n *api.SImportNic, net *SNetwork) *api.NetworkConfig {
return &api.NetworkConfig{
Network: net.Id,
Wire: net.WireId,
Address: n.Ip,
Mac: n.Mac,
Driver: n.Driver,
BwLimit: n.BandWidth,
}
}
func (self *SGuest) importNics(ctx context.Context, userCred mcclient.TokenCredential, nics []api.SImportNic) error {
if len(nics) == 0 {
return httperrors.NewInputParameterError("Empty import nics")
}
for _, nic := range nics {
q := GuestnetworkManager.Query()
count := q.Filter(sqlchemy.OR(
sqlchemy.Equals(q.Field("mac_addr"), nic.Mac),
sqlchemy.Equals(q.Field("ip_addr"), nic.Ip)),
).Count()
if count > 0 {
return httperrors.NewInputParameterError("ip %s or mac %s has been registered", nic.Ip, nic.Mac)
}
net, err := NetworkManager.GetOnPremiseNetworkOfIP(nic.Ip, "", tristate.None)
if err != nil {
return httperrors.NewNotFoundError("Not found network by ip %s", nic.Ip)
}
host, _ := self.GetHost()
_, err = self.attach2NetworkDesc(ctx, userCred, host, ToNetConfig(&nic, net), nil, nil)
if err != nil {
return err
}
}
return nil
}
func ToDiskConfig(d *api.SImportDisk) *api.DiskConfig {
ret := &api.DiskConfig{
SizeMb: d.SizeMb,
ImageId: d.TemplateId,
Format: d.Format,
Driver: d.Driver,
DiskId: d.DiskId,
Cache: d.CacheMode,
Backend: d.Backend,
}
if len(d.Mountpoint) > 0 {
ret.Mountpoint = d.Mountpoint
}
if len(d.Fs) > 0 {
ret.Fs = d.Fs
}
return ret
}
func (self *SGuest) importDisks(ctx context.Context, userCred mcclient.TokenCredential, disks []api.SImportDisk) error {
if len(disks) == 0 {
return httperrors.NewInputParameterError("Empty import disks")
}
host, _ := self.GetHost()
for _, disk := range disks {
disk, err := self.createDiskOnHost(ctx, userCred, host, ToDiskConfig(&disk), nil, true, true, nil, nil, true)
if err != nil {
return err
}
disk.SetStatus(ctx, userCred, api.DISK_READY, "")
}
return nil
}
// 从Libvirt导入虚拟机
func (manager *SGuestManager) PerformImportFromLibvirt(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
host := &api.SLibvirtHostConfig{}
if err := data.Unmarshal(host); err != nil {
return nil, httperrors.NewInputParameterError("unmarshal data failed %s", err)
}
if len(host.XmlFilePath) == 0 {
return nil, httperrors.NewInputParameterError("Some host config missing xml_file_path")
}
if len(host.HostIp) == 0 {
return nil, httperrors.NewInputParameterError("Some host config missing host ip")
}
sHost, err := HostManager.GetHostByIp("", api.HOST_TYPE_HYPERVISOR, host.HostIp)
if err != nil {
return nil, httperrors.NewInputParameterError("Invalid host ip %s", host.HostIp)
}
for _, server := range host.Servers {
for mac, ip := range server.MacIp {
_, err = net.ParseMAC(mac)
if err != nil {
return nil, httperrors.NewBadRequestError("Invalid server mac address %s", mac)
}
nIp := net.ParseIP(ip)
if nIp == nil {
return nil, httperrors.NewBadRequestError("Invalid server ip address %s", ip)
}
q := GuestnetworkManager.Query()
count := q.Filter(sqlchemy.OR(
sqlchemy.Equals(q.Field("mac_addr"), mac),
sqlchemy.Equals(q.Field("ip_addr"), ip)),
).Count()
if count > 0 {
return nil, httperrors.NewInputParameterError("ip %s or mac %s has been registered", mac, ip)
}
}
}
taskData := jsonutils.NewDict()
taskData.Set("xml_file_path", jsonutils.NewString(host.XmlFilePath))
taskData.Set("servers", jsonutils.Marshal(host.Servers))
taskData.Set("monitor_path", jsonutils.NewString(host.MonitorPath))
task, err := taskman.TaskManager.NewTask(ctx, "HostImportLibvirtServersTask", sHost, userCred,
taskData, "", "", nil)
if err != nil {
return nil, httperrors.NewInternalServerError("NewTask error: %s", err)
}
task.ScheduleRun(nil)
return nil, nil
}
func (self *SGuest) GetDetailsVirtInstall(
ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
if self.Hypervisor != api.HYPERVISOR_KVM {
return nil, httperrors.NewBadRequestError("Hypervisor %s can't generate libvirt xml", self.Hypervisor)
}
var (
vdiProtocol string
vdiListenPort int64
)
driver, err := self.GetDriver()
if err != nil {
return nil, err
}
host, _ := self.GetHost()
if utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_BLOCK_STREAM}) {
vncInfo, err := driver.GetGuestVncInfo(ctx, userCred, self, host, nil)
if err != nil {
log.Errorln(err)
return nil, err
}
vdiProtocol = vncInfo.Protocol
vdiListenPort = int64(vncInfo.Port)
}
extraCmdline, _ := query.GetArray("extra_cmdline")
libvirtBridge, _ := query.GetString("libvirt_bridge")
if len(libvirtBridge) == 0 {
libvirtBridge = "virbr0"
}
virtInstallCmd, err := self.GenerateVirtInstallCommandLine(
vdiProtocol, vdiListenPort, extraCmdline, libvirtBridge)
if err != nil {
return nil, httperrors.NewInternalServerError("Generate xml failed: %s", err)
}
res := jsonutils.NewDict()
res.Set("virt-install-command-line", jsonutils.NewString(virtInstallCmd))
return res, nil
}
func (self *SGuest) GenerateVirtInstallCommandLine(
vdiProtocol string, vdiListenPort int64, extraCmdline []jsonutils.JSONObject, libvirtBridge string,
) (string, error) {
L := func(s string) string { return s + " \\\n" }
cmd := L("virt-install")
cmd += L("--cpu host")
cmd += L("--boot cdrom,hd,network")
cmd += L(fmt.Sprintf("--name %s", self.Name))
cmd += L(fmt.Sprintf("--ram %d", self.VmemSize))
cmd += L(fmt.Sprintf("--vcpus %d", self.VcpuCount))
host, _ := self.GetHost()
// disks
guestDisks, err := self.GetGuestDisks()
if err != nil {
return "", errors.Wrapf(err, "GetGuestDisks")
}
for _, guestDisk := range guestDisks {
disk := guestDisk.GetDisk()
cmd += L(
fmt.Sprintf("--disk path=%s,bus=%s,cache=%s,io=%s",
disk.GetPathAtHost(host),
guestDisk.Driver,
guestDisk.CacheMode,
guestDisk.AioMode),
)
}
// networks
guestNetworks, err := self.GetNetworks("")
if err != nil {
return "", err
}
for _, guestNetwork := range guestNetworks {
cmd += L(
fmt.Sprintf("--network bridge,model=%s,source=%s,mac=%s",
guestNetwork.Driver, libvirtBridge, guestNetwork.MacAddr),
)
}
// isolated devices
isolatedDevices, _ := self.GetGuestIsolatedDevices()
for _, isolatedDev := range isolatedDevices {
dev := isolatedDev.GetIsolatedDevice()
cmd += L(fmt.Sprintf("--hostdev %s", dev.Addr))
}
if utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_BLOCK_STREAM}) {
// spice or vnc
cmd += L(fmt.Sprintf("--graphics %s,listen=0.0.0.0,port=%d", vdiProtocol, vdiListenPort))
} else {
// generate xml, and need virsh define xml
cmd += L("--print-xml")
}
cmd += L("--video vga")
cmd += L("--wait 0")
// some customized options, not verify
for _, cmdline := range extraCmdline {
cmd += L(cmdline.String())
}
// debug print
cmd += "-d"
return cmd, nil
}
func (self *SGuest) PerformSyncFixNics(ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input api.GuestSyncFixNicsInput) (jsonutils.JSONObject, error) {
iVM, err := self.GetIVM(ctx)
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
vnics, err := iVM.GetINics()
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
host, _ := self.GetHost()
if host == nil {
return nil, httperrors.NewInternalServerError("host not found")
}
iplist := input.Ip
// validate iplist
if len(iplist) == 0 {
return nil, httperrors.NewInputParameterError("empty ip list")
}
for _, ip := range iplist {
if !regutils.MatchIP4Addr(ip) {
return nil, httperrors.NewInputParameterError("invalid IPv4 address %s", ip)
}
// ip is reachable on host
net, err := host.getNetworkOfIPOnHost(ctx, ip)
if err != nil {
return nil, httperrors.NewInputParameterError("Unreachable IP %s: %s", ip, err)
}
// check ip is reserved or free
rip := ReservedipManager.GetReservedIP(net, ip, api.AddressTypeIPv4)
if rip == nil {
// check ip is free
nip, err := net.GetFreeIPWithLock(ctx, userCred, nil, nil, ip, "", false, api.AddressTypeIPv4)
if err != nil {
return nil, httperrors.NewInputParameterError("Unavailable IP %s: occupied", ip)
}
if nip != ip {
return nil, httperrors.NewInputParameterError("Unavailable IP %s: occupied", ip)
}
}
}
errs := make([]error, 0)
for i := range vnics {
ip := vnics[i].GetIP()
if len(ip) == 0 {
continue
}
_, err := host.getNetworkOfIPOnHost(ctx, ip)
if err != nil {
errs = append(errs, errors.Wrap(err, ip))
}
}
if len(errs) > 0 {
return nil, httperrors.NewInvalidStatusError("%v", errors.NewAggregate(errs))
}
result := self.SyncVMNics(ctx, userCred, host, vnics, iplist)
if result.IsError() {
return nil, httperrors.NewInternalServerError("%s", result.Result())
}
return nil, nil
}
// 更改项目
func (guest *SGuest) PerformChangeOwner(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PerformChangeProjectOwnerInput) (jsonutils.JSONObject, error) {
disks, err := guest.GetDisks()
if err != nil {
return nil, errors.Wrapf(err, "GetDisks")
}
for i := range disks {
disk := disks[i]
_, err := disk.PerformChangeOwner(ctx, userCred, query, input)
if err != nil {
return nil, err
}
}
if eip, _ := guest.GetEipOrPublicIp(); eip != nil {
_, err := eip.PerformChangeOwner(ctx, userCred, query, input)
if err != nil {
return nil, err
}
}
// change owner for instance snapshot
isps, err := guest.GetInstanceSnapshots()
if err != nil {
return nil, errors.Wrap(err, "unable to GetInstanceSnapshots")
}
for i := range isps {
_, err := isps[i].PerformChangeOwner(ctx, userCred, query, input)
if err != nil {
return nil, errors.Wrapf(err, "unable to change owner for instance snapshot %s", isps[i].GetId())
}
}
ctrs, err := GetContainerManager().GetContainersByPod(guest.GetId())
if err != nil {
return nil, errors.Wrapf(err, "get containers by guest_id %s", guest.GetId())
}
for _, ctr := range ctrs {
if _, err := ctr.PerformChangeOwner(ctx, userCred, query, input); err != nil {
return nil, errors.Wrapf(err, "unable to change owner for container %s", ctr.GetName())
}
}
changOwner, err := guest.SVirtualResourceBase.PerformChangeOwner(ctx, userCred, query, input)
if err != nil {
return nil, err
}
err = guest.StartSyncTask(ctx, userCred, false, "")
if err != nil {
return nil, errors.Wrap(err, "PerformChangeOwner StartSyncTask err")
}
return changOwner, nil
}
// 磁盘扩容
func (guest *SGuest) PerformResizeDisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerResizeDiskInput) (jsonutils.JSONObject, error) {
if guest.Hypervisor == api.HYPERVISOR_ESXI {
c, err := guest.GetInstanceSnapshotCount()
if err != nil {
return nil, errors.Wrapf(err, "unable to GetInstanceSnapshotCount for guest %s", guest.GetName())
}
if c > 0 {
return nil, httperrors.NewUnsupportOperationError("ESXi VM disks with instance snapshots do not support resizing")
}
}
if len(input.DiskId) == 0 {
return nil, httperrors.NewMissingParameterError("disk_id")
}
diskObj, err := validators.ValidateModel(ctx, userCred, DiskManager, &input.DiskId)
if err != nil {
return nil, err
}
guestdisk := guest.GetGuestDisk(input.DiskId)
if guestdisk == nil {
return nil, httperrors.NewInvalidStatusError("disk %s not attached to server", input.DiskId)
}
disk := diskObj.(*SDisk)
sizeMb, err := input.SizeMb()
if err != nil {
return nil, err
}
err = disk.doResize(ctx, userCred, sizeMb, guest)
if err != nil {
return nil, err
}
return nil, nil
}
func (guest *SGuest) StartGuestDiskResizeTask(ctx context.Context, userCred mcclient.TokenCredential, diskId string, sizeMb int64, parentTaskId string, pendingUsage quotas.IQuota) error {
guest.SetStatus(ctx, userCred, api.VM_START_RESIZE_DISK, "StartGuestDiskResizeTask")
params := jsonutils.NewDict()
params.Add(jsonutils.NewInt(sizeMb), "size")
params.Add(jsonutils.NewString(diskId), "disk_id")
task, err := taskman.TaskManager.NewTask(ctx, "GuestResizeDiskTask", guest, userCred, params, parentTaskId, "", pendingUsage)
if err != nil {
return err
}
task.ScheduleRun(nil)
return nil
}
// 磁盘限速
func (self *SGuest) PerformIoThrottle(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.ServerSetDiskIoThrottleInput) (jsonutils.JSONObject, error) {
if self.Hypervisor != api.HYPERVISOR_KVM {
return nil, httperrors.NewBadRequestError("Hypervisor %s can't do io throttle", self.Hypervisor)
}
if self.Status != api.VM_RUNNING && self.Status != api.VM_READY {
return nil, httperrors.NewServerStatusError("Cannot do io throttle in status %s", self.Status)
}
for diskId, bpsMb := range input.Bps {
if bpsMb < 0 {
return nil, httperrors.NewInputParameterError("disk %s bps must > 0", diskId)
}
disk := DiskManager.FetchDiskById(diskId)
if disk == nil {
return nil, httperrors.NewNotFoundError("disk %s not found", diskId)
}
}
for diskId, iops := range input.IOPS {
if iops < 0 {
return nil, httperrors.NewInputParameterError("disk %s iops must > 0", diskId)
}
disk := DiskManager.FetchDiskById(diskId)
if disk == nil {
return nil, httperrors.NewNotFoundError("disk %s not found", diskId)
}
}
if err := self.UpdateIoThrottle(input); err != nil {
return nil, errors.Wrap(err, "update io throttles")
}
return nil, self.StartBlockIoThrottleTask(ctx, userCred, input)
}
func (self *SGuest) UpdateIoThrottle(input *api.ServerSetDiskIoThrottleInput) error {
gds, err := self.GetGuestDisks()
if err != nil {
return err
}
for i := 0; i < len(gds); i++ {
_, err := db.Update(&gds[i], func() error {
if bps, ok := input.Bps[gds[i].DiskId]; ok {
gds[i].Bps = bps
}
if iops, ok := input.IOPS[gds[i].DiskId]; ok {
gds[i].Iops = iops
}
return nil
})
if err != nil {
return nil
}
}
return nil
}
func (self *SGuest) StartBlockIoThrottleTask(ctx context.Context, userCred mcclient.TokenCredential, input *api.ServerSetDiskIoThrottleInput) error {
params := jsonutils.Marshal(input).(*jsonutils.JSONDict)
params.Set("old_status", jsonutils.NewString(self.Status))
self.SetStatus(ctx, userCred, api.VM_IO_THROTTLE, "start block io throttle task")
task, err := taskman.TaskManager.NewTask(ctx, "GuestBlockIoThrottleTask", self, userCred, params, "", "", nil)
if err != nil {
log.Errorf("%s", err)
return err
}
task.ScheduleRun(nil)
return nil
}
func (self *SGuest) validateForBatchMigrate(ctx context.Context, rescueMode bool) (*SGuest, error) {
guest := GuestManager.FetchGuestById(self.Id)
if guest.Hypervisor != api.HYPERVISOR_KVM {
return guest, httperrors.NewBadRequestError("guest %s hypervisor %s can't migrate",
guest.Name, guest.Hypervisor)
}
if len(guest.BackupHostId) > 0 {
return guest, httperrors.NewBadRequestError("guest %s has backup, can't migrate", guest.Name)
}
devs, _ := guest.GetGuestIsolatedDevices()
if len(devs) > 0 {
return guest, httperrors.NewBadRequestError("guest %s has isolated device, can't migrate", guest.Name)
}
if rescueMode {
if !guest.guestDisksStorageTypeIsShared() {
return guest, httperrors.NewBadRequestError("cannot rescue guest %s with local storage", guest.Name)
}
return guest, nil
}
if !utils.IsInStringArray(guest.Status, []string{api.VM_RUNNING, api.VM_READY, api.VM_UNKNOWN}) {
return guest, httperrors.NewBadRequestError("guest %s status %s can't migrate", guest.Name, guest.Status)
}
if guest.Status == api.VM_RUNNING {
cdroms, _ := guest.getCdroms()
for _, cdrom := range cdroms {
if len(cdrom.ImageId) > 0 {
return guest, httperrors.NewBadRequestError("cannot migrate with cdrom")
}
}
floppys, _ := guest.getFloppys()
for _, floppy := range floppys {
if len(floppy.ImageId) > 0 {
return guest, httperrors.NewBadRequestError("cannot migrate with floppy")
}
}
} else if guest.Status == api.VM_UNKNOWN {
if guest.getDefaultStorageType() == api.STORAGE_LOCAL {
return guest, httperrors.NewBadRequestError(
"guest %s status %s can't migrate with local storage",
guest.Name, guest.Status,
)
}
}
return guest, nil
}
// 批量迁移
func (manager *SGuestManager) PerformBatchMigrate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
params := new(api.GuestBatchMigrateRequest)
err := data.Unmarshal(params)
if err != nil {
return nil, httperrors.NewInputParameterError("unmarshal input failed %s", err)
}
if len(params.GuestIds) == 0 {
return nil, httperrors.NewInputParameterError("missing guest id")
}
var preferHostId string
if len(params.PreferHostId) > 0 {
iHost, _ := HostManager.FetchByIdOrName(ctx, userCred, params.PreferHostId)
if iHost == nil {
return nil, httperrors.NewBadRequestError("Host %s not found", params.PreferHostId)
}
host := iHost.(*SHost)
preferHostId = host.Id
err := host.IsAssignable(ctx, userCred)
if err != nil {
return nil, errors.Wrap(err, "IsAssignable")
}
}
if params.EnableTLS == nil {
params.EnableTLS = &options.Options.EnableTlsMigration
}
guests := make([]SGuest, 0)
q := GuestManager.Query().In("id", params.GuestIds)
err = db.FetchModelObjects(GuestManager, q, &guests)
if err != nil {
return nil, httperrors.NewInternalServerError("%v", err)
}
if len(guests) != len(params.GuestIds) {
return nil, httperrors.NewBadRequestError("Check input guests is exist")
}
errs := []error{}
for i := 0; i < len(guests); i++ {
func() {
lockman.LockObject(ctx, &guests[i])
defer lockman.ReleaseObject(ctx, &guests[i])
_, err = guests[i].validateForBatchMigrate(ctx, false)
if err != nil {
errs = append(errs, errors.Wrapf(err, "Guest %s", guests[i].Name))
return
}
if guests[i].Status == api.VM_RUNNING {
err = guests[i].StartGuestLiveMigrateTask(ctx, userCred,
guests[i].Status, preferHostId, &params.SkipCpuCheck, &params.SkipKernelCheck,
params.EnableTLS, params.QuciklyFinish, params.MaxBandwidthMb, nil, "",
)
} else {
err = guests[i].StartMigrateTask(ctx, userCred, guests[i].Status == api.VM_UNKNOWN,
false, guests[i].Status, preferHostId, "")
}
if err != nil {
errs = append(errs, errors.Wrapf(err, "Guest %s", guests[i].Name))
}
}()
}
return nil, errors.NewAggregate(errs)
}
func (manager *SGuestManager) StartHostGuestsMigrateTask(
ctx context.Context, userCred mcclient.TokenCredential,
guests []*SGuest, kwargs *jsonutils.JSONDict, parentTaskId string,
) error {
taskItems := make([]db.IStandaloneModel, len(guests))
for i := range guests {
taskItems[i] = guests[i]
}
task, err := taskman.TaskManager.NewParallelTask(ctx, "HostGuestsMigrateTask", taskItems, userCred, kwargs, parentTaskId, "", nil)
if err != nil {
log.Errorln(err)
return err
}
task.ScheduleRun(nil)
return nil
}
var supportInstanceSnapshotHypervisors = []string{
api.HYPERVISOR_KVM,
api.HYPERVISOR_ESXI,
api.HYPERVISOR_CNWARE,
}
func (self *SGuest) validateCreateInstanceSnapshot(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input api.ServerCreateSnapshotParams,
) (*SRegionQuota, api.ServerCreateSnapshotParams, error) {
if !utils.IsInStringArray(self.Hypervisor, supportInstanceSnapshotHypervisors) {
return nil, input, httperrors.NewBadRequestError("guest hypervisor %s cannot create instance snapshot", self.Hypervisor)
}
disks, err := self.GetDisks()
if err != nil {
return nil, input, err
}
for i := range disks {
if len(disks[i].SnapshotId) > 0 {
if disks[i].GetMetadata(ctx, "merge_snapshot", userCred) == "true" {
return nil, input, httperrors.NewBadRequestError("disk %s backing snapshot not merged", disks[i].Id)
}
}
}
if len(self.BackupHostId) > 0 || self.GetMetadata(ctx, api.QUORUM_CHILD_INDEX, userCred) != "" {
return nil, input, httperrors.NewBadRequestError("Can't do instance snapshot with backup guest")
}
if !utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_READY}) {
return nil, input, httperrors.NewInvalidStatusError("guest can't do snapshot in status %s", self.Status)
}
ownerId := self.GetOwnerId()
// dataDict := data.(*jsonutils.JSONDict)
// nameHint, err := dataDict.GetString("generate_name")
if len(input.GenerateName) > 0 {
name, err := db.GenerateName(ctx, InstanceSnapshotManager, ownerId, input.GenerateName)
if err != nil {
return nil, input, errors.Wrap(err, "GenerateName")
}
input.Name = name
} else if len(input.Name) == 0 {
return nil, input, httperrors.NewMissingParameterError("name")
}
err = db.NewNameValidator(ctx, InstanceSnapshotManager, ownerId, input.Name, nil)
if err != nil {
return nil, input, errors.Wrap(err, "NewNameValidator")
}
// construct Quota
pendingUsage := &SRegionQuota{InstanceSnapshot: 1}
host, _ := self.GetHost()
provider := host.GetProviderName()
if utils.IsInStringArray(provider, ProviderHasSubSnapshot) {
disks, err := self.GetDisks()
if err != nil {
return nil, input, errors.Wrapf(err, "GetDisks")
}
//for i := 0; i < len(disks); i++ {
// if storage, _ := disks[i].GetStorage(); utils.IsInStringArray(storage.StorageType, api.FIEL_STORAGE) {
// count, err := SnapshotManager.GetDiskManualSnapshotCount(disks[i].Id)
// if err != nil {
// return nil, input, httperrors.NewInternalServerError("%v", err)
// }
// if count >= options.Options.DefaultMaxManualSnapshotCount {
// return nil, input, httperrors.NewBadRequestError("guests disk %d snapshot full, can't take anymore", i)
// }
// }
//}
pendingUsage.Snapshot = len(disks)
}
keys, err := self.GetRegionalQuotaKeys()
if err != nil {
return nil, input, errors.Wrap(err, "GetRegionalQuotaKeys")
}
pendingUsage.SetKeys(keys)
err = quotas.CheckSetPendingQuota(ctx, userCred, pendingUsage)
if err != nil {
return nil, input, httperrors.NewOutOfQuotaError("check set pending quota failed %s", err)
}
return pendingUsage, input, nil
}
func (self *SGuest) validateCreateInstanceBackup(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input api.ServerCreateInstanceBackupInput,
) (api.ServerCreateInstanceBackupInput, error) {
if !utils.IsInStringArray(self.Hypervisor, []string{api.HYPERVISOR_KVM}) {
return input, httperrors.NewBadRequestError("guest hypervisor %s cannot create instance snapshot", self.Hypervisor)
}
if len(self.BackupHostId) > 0 {
return input, httperrors.NewBadRequestError("Can't do instance snapshot with backup guest")
}
if !utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_READY}) {
return input, httperrors.NewInvalidStatusError("guest can't do snapshot in status %s", self.Status)
}
ownerId := self.GetOwnerId()
if len(input.GenerateName) > 0 {
nameHint := input.GenerateName
name, err := db.GenerateName(ctx, InstanceBackupManager, ownerId, nameHint)
if err != nil {
return input, errors.Wrap(err, "db.GenerateName")
}
input.Name = name
} else if len(input.Name) == 0 {
return input, httperrors.NewMissingParameterError("name")
}
err := db.NewNameValidator(ctx, InstanceBackupManager, ownerId, input.Name, nil)
if err != nil {
return input, errors.Wrap(err, "db.NewNameValidator")
}
return input, nil
}
// 创建主机快照
// 1. validate guest status, guest hypervisor
// 2. validate every disk manual snapshot count
// 3. validate snapshot quota with disk count
func (self *SGuest) PerformInstanceSnapshot(
ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerInstanceSnapshot,
) (jsonutils.JSONObject, error) {
if err := self.ValidateEncryption(ctx, userCred); err != nil {
return nil, errors.Wrap(httperrors.ErrForbidden, "encryption key not accessible")
}
lockman.LockClass(ctx, InstanceSnapshotManager, userCred.GetProjectId())
defer lockman.ReleaseClass(ctx, InstanceSnapshotManager, userCred.GetProjectId())
pendingUsage, params, err := self.validateCreateInstanceSnapshot(ctx, userCred, query, input.ServerCreateSnapshotParams)
if err != nil {
return nil, errors.Wrap(err, "validateCreateInstanceSnapshot")
}
input.ServerCreateSnapshotParams = params
if input.WithMemory {
if self.Status != api.VM_RUNNING {
return nil, httperrors.NewUnsupportOperationError("Can't save memory state when guest status is %q", self.Status)
}
}
instanceSnapshot, err := InstanceSnapshotManager.CreateInstanceSnapshot(ctx, userCred, self, input.Name, false, input.WithMemory)
if err != nil {
quotas.CancelPendingUsage(
ctx, userCred, pendingUsage, pendingUsage, false)
return nil, httperrors.NewInternalServerError("create instance snapshot failed: %s", err)
}
err = self.InheritTo(ctx, userCred, instanceSnapshot)
if err != nil {
return nil, errors.Wrapf(err, "unable to inherit from guest %s to instance snapshot %s", self.GetId(), instanceSnapshot.GetId())
}
err = self.InstaceCreateSnapshot(ctx, userCred, instanceSnapshot, pendingUsage)
if err != nil {
quotas.CancelPendingUsage(
ctx, userCred, pendingUsage, pendingUsage, false)
return nil, httperrors.NewInternalServerError("start create snapshot task failed: %s", err)
}
return nil, nil
}
func (self *SGuest) PerformInstanceBackup(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input api.ServerCreateInstanceBackupInput,
) (jsonutils.JSONObject, error) {
if err := self.ValidateEncryption(ctx, userCred); err != nil {
return nil, errors.Wrap(httperrors.ErrForbidden, "encryption key not accessible")
}
lockman.LockClass(ctx, InstanceSnapshotManager, userCred.GetProjectId())
defer lockman.ReleaseClass(ctx, InstanceSnapshotManager, userCred.GetProjectId())
var err error
input, err = self.validateCreateInstanceBackup(ctx, userCred, query, input)
if err != nil {
return nil, errors.Wrap(err, "validateCreateInstanceBackup")
}
name := input.Name
backupStorageId := input.BackupStorageId
if backupStorageId == "" {
return nil, httperrors.NewMissingParameterError("backup_storage_id")
}
ibs, err := BackupStorageManager.FetchByIdOrName(ctx, userCred, backupStorageId)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, httperrors.NewResourceNotFoundError2(BackupStorageManager.Keyword(), backupStorageId)
}
if errors.Cause(err) == sqlchemy.ErrDuplicateEntry {
return nil, httperrors.NewDuplicateResourceError(BackupStorageManager.Keyword(), backupStorageId)
}
return nil, httperrors.NewGeneralError(err)
}
bs := ibs.(*SBackupStorage)
if bs.Status != api.BACKUPSTORAGE_STATUS_ONLINE {
return nil, httperrors.NewForbiddenError("can't backup guest to backup storage with status %s", bs.Status)
}
instanceBackup, err := InstanceBackupManager.CreateInstanceBackup(ctx, userCred, self, name, backupStorageId)
if err != nil {
return nil, httperrors.NewInternalServerError("create instance backup failed: %s", err)
}
err = self.InheritTo(ctx, userCred, instanceBackup)
if err != nil {
return nil, errors.Wrapf(err, "unable to inherit from guest %s to instance backup %s", self.GetId(), instanceBackup.GetId())
}
err = self.InstanceCreateBackup(ctx, userCred, instanceBackup)
if err != nil {
return nil, httperrors.NewInternalServerError("start create backup task failed: %s", err)
}
return nil, nil
}
func (self *SGuest) InstaceCreateSnapshot(
ctx context.Context,
userCred mcclient.TokenCredential,
instanceSnapshot *SInstanceSnapshot,
pendingUsage *SRegionQuota,
) error {
self.SetStatus(ctx, userCred, api.VM_START_INSTANCE_SNAPSHOT, "instance snapshot")
return instanceSnapshot.StartCreateInstanceSnapshotTask(ctx, userCred, pendingUsage, "")
}
func (self *SGuest) InstanceCreateBackup(ctx context.Context, userCred mcclient.TokenCredential, instanceBackup *SInstanceBackup) error {
self.SetStatus(ctx, userCred, api.VM_START_INSTANCE_BACKUP, "instance backup")
return instanceBackup.StartCreateInstanceBackupTask(ctx, userCred, "")
}
func (self *SGuest) PerformInstanceSnapshotReset(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerResetInput) (jsonutils.JSONObject, error) {
if err := self.ValidateEncryption(ctx, userCred); err != nil {
return nil, errors.Wrap(httperrors.ErrForbidden, "encryption key not accessible")
}
if self.Status != api.VM_READY {
return nil, httperrors.NewInvalidStatusError("guest can't do snapshot in status %s", self.Status)
}
obj, err := InstanceSnapshotManager.FetchByIdOrName(ctx, userCred, input.InstanceSnapshot)
if err != nil {
return nil, errors.Wrapf(err, "unable to fetch instance snapshot %q", input.InstanceSnapshot)
}
instanceSnapshot := obj.(*SInstanceSnapshot)
if instanceSnapshot.GuestId != self.GetId() {
return nil, httperrors.NewBadRequestError("instance snapshot %q not belong server %q", instanceSnapshot.GetName(), self.GetId())
}
if instanceSnapshot.Status != api.INSTANCE_SNAPSHOT_READY {
return nil, httperrors.NewBadRequestError("Instance snapshot not ready")
}
if input.WithMemory && !instanceSnapshot.WithMemory {
return nil, httperrors.NewBadRequestError("Instance snapshot not with memory statefile")
}
err = self.StartSnapshotResetTask(ctx, userCred, instanceSnapshot, input.AutoStart, input.WithMemory)
if err != nil {
return nil, httperrors.NewInternalServerError("start snapshot reset failed %s", err)
}
return nil, nil
}
func (self *SGuest) StartSnapshotResetTask(ctx context.Context, userCred mcclient.TokenCredential, instanceSnapshot *SInstanceSnapshot, autoStart *bool, withMemory bool) error {
data := jsonutils.NewDict()
if autoStart != nil && *autoStart {
data.Set("auto_start", jsonutils.JSONTrue)
}
data.Add(jsonutils.NewBool(withMemory), "with_memory")
self.SetStatus(ctx, userCred, api.VM_START_SNAPSHOT_RESET, "start snapshot reset task")
instanceSnapshot.SetStatus(ctx, userCred, api.INSTANCE_SNAPSHOT_RESET, "start snapshot reset task")
if task, err := taskman.TaskManager.NewTask(
ctx, "InstanceSnapshotResetTask", instanceSnapshot, userCred, data, "", "", nil,
); err != nil {
return err
} else {
task.ScheduleRun(nil)
}
return nil
}
func (self *SGuest) PerformSnapshotAndClone(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input api.ServerSnapshotAndCloneInput,
) (jsonutils.JSONObject, error) {
if err := self.ValidateEncryption(ctx, userCred); err != nil {
return nil, errors.Wrap(httperrors.ErrForbidden, "encryption key not accessible")
}
newlyGuestName := input.Name
if len(input.Name) == 0 {
return nil, httperrors.NewMissingParameterError("name")
}
count := 1
if input.Count != nil {
count = *input.Count
if count <= 0 {
return nil, httperrors.NewInputParameterError("count must > 0")
}
}
if len(input.PreferHostId) > 0 {
if len(input.PreferHostId) > 0 {
iHost, _ := HostManager.FetchByIdOrName(ctx, userCred, input.PreferHostId)
if iHost == nil {
return nil, httperrors.NewBadRequestError("Host %s not found", input.PreferHostId)
}
host := iHost.(*SHost)
input.PreferHostId = host.Id
}
}
lockman.LockRawObject(ctx, InstanceSnapshotManager.Keyword(), "name")
defer lockman.ReleaseRawObject(ctx, InstanceSnapshotManager.Keyword(), "name")
// validate create instance snapshot and set snapshot pending usage
snapshotUsage, params, err := self.validateCreateInstanceSnapshot(ctx, userCred, query, input.ServerCreateSnapshotParams)
if err != nil {
return nil, errors.Wrap(err, "validateCreateInstanceSnapshot")
}
input.ServerCreateSnapshotParams = params
// set guest pending usage
pendingUsage, pendingRegionUsage, err := self.getGuestUsage(count)
if err != nil {
return nil, errors.Wrap(err, "getGuestUsage")
}
keys, err := self.GetQuotaKeys()
if err != nil {
quotas.CancelPendingUsage(ctx, userCred, snapshotUsage, snapshotUsage, false)
return nil, errors.Wrap(err, "GetQuotaKeys")
}
pendingUsage.SetKeys(keys)
err = quotas.CheckSetPendingQuota(ctx, userCred, &pendingUsage)
if err != nil {
quotas.CancelPendingUsage(ctx, userCred, snapshotUsage, snapshotUsage, false)
return nil, httperrors.NewOutOfQuotaError("check set pending quota failed %s", err)
}
regionKeys, err := self.GetRegionalQuotaKeys()
if err != nil {
quotas.CancelPendingUsage(ctx, userCred, snapshotUsage, snapshotUsage, false)
quotas.CancelPendingUsage(ctx, userCred, &pendingUsage, &pendingUsage, false)
return nil, errors.Wrap(err, "GetRegionalQuotaKeys")
}
pendingRegionUsage.SetKeys(regionKeys)
err = quotas.CheckSetPendingQuota(ctx, userCred, &pendingRegionUsage)
if err != nil {
quotas.CancelPendingUsage(ctx, userCred, snapshotUsage, snapshotUsage, false)
quotas.CancelPendingUsage(ctx, userCred, &pendingUsage, &pendingUsage, false)
return nil, errors.Wrap(err, "CheckSetPendingQuota")
}
// migrate snapshotUsage into regionUsage, then discard snapshotUsage
pendingRegionUsage.Snapshot = snapshotUsage.Snapshot
instanceSnapshotName, err := db.GenerateName(ctx, InstanceSnapshotManager, self.GetOwnerId(),
fmt.Sprintf("%s-%s", input.Name, rand.String(8)))
if err != nil {
quotas.CancelPendingUsage(ctx, userCred, &pendingUsage, &pendingUsage, false)
quotas.CancelPendingUsage(ctx, userCred, &pendingRegionUsage, &pendingRegionUsage, false)
return nil, httperrors.NewInternalServerError("Generate snapshot name failed %s", err)
}
instanceSnapshot, err := InstanceSnapshotManager.CreateInstanceSnapshot(
ctx, userCred, self, instanceSnapshotName,
input.AutoDeleteInstanceSnapshot != nil && *input.AutoDeleteInstanceSnapshot, false)
if err != nil {
quotas.CancelPendingUsage(ctx, userCred, &pendingUsage, &pendingUsage, false)
quotas.CancelPendingUsage(ctx, userCred, &pendingRegionUsage, &pendingRegionUsage, false)
return nil, httperrors.NewInternalServerError("create instance snapshot failed: %s", err)
} else {
cancelRegionUsage := &SRegionQuota{Snapshot: snapshotUsage.Snapshot}
quotas.CancelPendingUsage(ctx, userCred, &pendingRegionUsage, cancelRegionUsage, true)
}
err = self.StartInstanceSnapshotAndCloneTask(
ctx, userCred, newlyGuestName, &pendingUsage, &pendingRegionUsage, instanceSnapshot, input)
if err != nil {
quotas.CancelPendingUsage(ctx, userCred, &pendingUsage, &pendingUsage, false)
quotas.CancelPendingUsage(ctx, userCred, &pendingRegionUsage, &pendingRegionUsage, false)
return nil, err
}
return nil, nil
}
func (self *SGuest) StartInstanceSnapshotAndCloneTask(
ctx context.Context, userCred mcclient.TokenCredential, newlyGuestName string,
pendingUsage *SQuota, pendingRegionUsage *SRegionQuota, instanceSnapshot *SInstanceSnapshot,
input api.ServerSnapshotAndCloneInput,
) error {
params := jsonutils.NewDict()
params.Set("guest_params", jsonutils.Marshal(input))
if task, err := taskman.TaskManager.NewTask(
ctx, "InstanceSnapshotAndCloneTask", instanceSnapshot, userCred, params, "", "", pendingUsage, pendingRegionUsage); err != nil {
return err
} else {
self.SetStatus(ctx, userCred, api.VM_START_INSTANCE_SNAPSHOT, "instance snapshot")
task.ScheduleRun(nil)
return nil
}
}
func (manager *SGuestManager) CreateGuestFromInstanceSnapshot(
ctx context.Context, userCred mcclient.TokenCredential, input api.ServerSnapshotAndCloneInput, isp *SInstanceSnapshot,
) (*SGuest, *jsonutils.JSONDict, error) {
lockman.LockRawObject(ctx, manager.Keyword(), "name")
defer lockman.ReleaseRawObject(ctx, manager.Keyword(), "name")
if guestName, err := db.GenerateName(ctx, manager, isp.GetOwnerId(), input.Name); err != nil {
return nil, nil, errors.Wrap(err, "db.GenerateName")
} else {
input.Name = guestName
}
input.InstanceSnapshotId = isp.Id
params := jsonutils.Marshal(input).(*jsonutils.JSONDict)
iGuest, err := db.DoCreate(manager, ctx, userCred, nil, params, isp.GetOwnerId())
if err != nil {
return nil, nil, errors.Wrap(err, "db.DoCreate")
}
guest := iGuest.(*SGuest)
notes := map[string]string{
"instance_snapshot_id": isp.Id,
"guest_id": isp.GuestId,
}
logclient.AddActionLogWithContext(ctx, guest, logclient.ACT_VM_SNAPSHOT_AND_CLONE, notes, userCred, true)
func() {
lockman.LockObject(ctx, guest)
defer lockman.ReleaseObject(ctx, guest)
guest.PostCreate(ctx, userCred, guest.GetOwnerId(), nil, params)
}()
return guest, params, nil
}
func (self *SGuest) GetDetailsJnlp(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if self.Hypervisor != api.HYPERVISOR_BAREMETAL {
return nil, httperrors.NewInvalidStatusError("not a baremetal server")
}
host, _ := self.GetHost()
if host == nil {
return nil, httperrors.NewInvalidStatusError("no valid host")
}
if !host.IsBaremetal {
return nil, httperrors.NewInvalidStatusError("host is not a baremetal")
}
return host.GetDetailsJnlp(ctx, userCred, query)
}
func (guest *SGuest) StartDeleteGuestSnapshots(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, deletePendingSnapshots bool) error {
data := jsonutils.NewDict()
if deletePendingSnapshots {
data.Add(jsonutils.JSONTrue, "delete_pending_snapshots")
}
task, err := taskman.TaskManager.NewTask(ctx, "GuestDeleteSnapshotsTask", guest, userCred, data, parentTaskId, "", nil)
if err != nil {
return err
}
task.ScheduleRun(nil)
return nil
}
func (guest *SGuest) ValidateChangeNicBillingModeInput(ctx context.Context, input api.ServerNicTrafficLimit, needResetTraffic bool) (api.ServerNicTrafficLimit, bool, error) {
var err error
var gn *SGuestnetwork
if len(input.Mac) == 0 {
gns, err := guest.GetNetworks("")
if err != nil {
return input, needResetTraffic, errors.Wrap(err, "get guest networks")
}
if len(gns) == 0 {
return input, needResetTraffic, httperrors.NewBadRequestError("no guest network found")
}
if len(gns) > 1 {
return input, needResetTraffic, httperrors.NewBadRequestError("multiple guest networks found, please specify the mac")
}
input.Mac = gns[0].MacAddr
gn = &gns[0]
} else {
input.Mac = netutils2.FormatMac(input.Mac)
gn, err = guest.GetGuestnetworkByMac(input.Mac)
if err != nil {
return input, needResetTraffic, errors.Wrap(err, "get guest network by mac")
}
}
if input.BillingType == "" {
input.BillingType = gn.BillingType
}
if input.ChargeType == "" {
input.ChargeType = gn.ChargeType
}
var changed bool
input, changed, err = input.Validate(gn.BillingType, gn.ChargeType, gn.TxTrafficLimit, gn.RxTrafficLimit)
if err != nil {
return input, needResetTraffic, errors.Wrap(err, "validate input")
}
if changed {
if gn.BillingType == billing_api.BILLING_TYPE_POSTPAID && gn.ChargeType == billing_api.NET_CHARGE_TYPE_BY_TRAFFIC {
// used to be postpaid traffic billing, need to stop the traffic log
// do nothing
}
if input.BillingType == billing_api.BILLING_TYPE_POSTPAID && input.ChargeType == billing_api.NET_CHARGE_TYPE_BY_TRAFFIC {
// need to start the traffic log
GuestNetworkTrafficLogManager.logTraffic(ctx, guest, gn, nil, time.Now(), true)
}
if input.ChargeType == billing_api.NET_CHARGE_TYPE_BY_TRAFFIC {
// need to measure the traffic from zero
needResetTraffic = true
}
}
return input, needResetTraffic, nil
}
func (guest *SGuest) doChangeNicBillingMode(ctx context.Context, userCred mcclient.TokenCredential, input api.ServerNicTrafficLimit, needResetTraffic bool, action string) error {
if !utils.IsInStringArray(guest.Status, []string{api.VM_READY, api.VM_RUNNING}) {
return httperrors.NewUnsupportOperationError("The guest status need be %s or %s, current is %s", api.VM_READY, api.VM_RUNNING, guest.Status)
}
var err error
input, needResetTraffic, err = guest.ValidateChangeNicBillingModeInput(ctx, input, needResetTraffic)
if err != nil {
return errors.Wrap(err, "validateChangeNicBillingModeInput")
}
taskName := "GuestSetNicTrafficsTask"
if needResetTraffic {
taskName = "GuestResetNicTrafficsTask"
}
params := jsonutils.Marshal(input).(*jsonutils.JSONDict)
params.Set("old_status", jsonutils.NewString(guest.Status))
guest.SetStatus(ctx, userCred, api.VM_SYNC_TRAFFIC_LIMIT, action)
task, err := taskman.TaskManager.NewTask(ctx, taskName, guest, userCred, params, "", "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
task.ScheduleRun(nil)
return nil
}
// 重置网卡计费方式
func (guest *SGuest) PerformResetNicTrafficLimit(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, input api.ServerNicTrafficLimit) (jsonutils.JSONObject, error) {
err := guest.doChangeNicBillingMode(ctx, userCred, input, true, "PerformResetNicTrafficLimit")
if err != nil {
return nil, errors.Wrap(err, "doChangeNicBillingMode")
}
return nil, nil
}
// 设置网卡计费方式
func (guest *SGuest) PerformSetNicTrafficLimit(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, input api.ServerNicTrafficLimit) (jsonutils.JSONObject, error) {
err := guest.doChangeNicBillingMode(ctx, userCred, input, false, "PerformSetNicTrafficLimit")
if err != nil {
return nil, errors.Wrap(err, "doChangeNicBillingMode")
}
return nil, nil
}
func (self *SGuest) PerformBindGroups(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
groupIdSet, err := self.checkGroups(ctx, userCred, query, data)
if err != nil {
return nil, err
}
groupGuests, err := GroupguestManager.FetchByGuestId(self.Id)
if err != nil {
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_INSTANCE_GROUP_BIND, nil, userCred, false)
return nil, err
}
for i := range groupGuests {
groupId := groupGuests[i].GroupId
if groupIdSet.Has(groupId) {
groupIdSet.Delete(groupId)
}
}
for _, groupId := range groupIdSet.UnsortedList() {
_, err := GroupguestManager.Attach(ctx, groupId, self.Id)
if err != nil {
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_INSTANCE_GROUP_BIND, nil, userCred, false)
return nil, errors.Wrapf(err, "fail to attch group %s to guest %s", groupId, self.Id)
}
}
// ignore error
err = self.ClearSchedDescCache()
if err != nil {
log.Errorf("fail to clear scheduler desc cache after unbinding groups successfully")
}
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_INSTANCE_GROUP_BIND, nil, userCred, true)
return nil, nil
}
func (self *SGuest) PerformUnbindGroups(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
groupIdSet, err := self.checkGroups(ctx, userCred, query, data)
if err != nil {
return nil, err
}
groupGuests, err := GroupguestManager.FetchByGuestId(self.Id)
if err != nil {
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_INSTANCE_GROUP_UNBIND, nil, userCred, false)
return nil, err
}
for i := range groupGuests {
joint := groupGuests[i]
if !groupIdSet.Has(joint.GroupId) {
continue
}
err := joint.Detach(ctx, userCred)
if err != nil {
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_INSTANCE_GROUP_UNBIND, nil, userCred, false)
return nil, errors.Wrapf(err, "fail to detach group %s to guest %s", joint.GroupId, self.Id)
}
}
// ignore error
err = self.ClearSchedDescCache()
if err != nil {
log.Errorf("fail to clear scheduler desc cache after binding groups successfully")
}
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_INSTANCE_GROUP_UNBIND, nil, userCred, true)
return nil, nil
}
// 绑定主机快照策略
// 主机只能绑定一个快照策略,已绑定时报错
// 若主机下任意磁盘已绑定快照策略则报错
func (self *SGuest) PerformBindSnapshotpolicy(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, input *api.ServerSnapshotpolicyInput) (jsonutils.JSONObject, error) {
if len(input.SnapshotpolicyId) == 0 {
return nil, httperrors.NewMissingParameterError("snapshotpolicy_id")
}
spObj, err := validators.ValidateModel(ctx, userCred, SnapshotPolicyManager, &input.SnapshotpolicyId)
if err != nil {
return nil, err
}
sp := spObj.(*SSnapshotPolicy)
if sp.Type != api.SNAPSHOT_POLICY_TYPE_SERVER {
return nil, httperrors.NewBadRequestError("The snapshot policy %s is not a server snapshot policy", sp.Name)
}
// 主机只能绑定一个快照策略
cnt, err := SnapshotPolicyResourceManager.GetBindingCount(self.Id, api.SNAPSHOT_POLICY_TYPE_SERVER)
if err != nil {
return nil, errors.Wrap(err, "GetBindingCount")
}
if cnt > 0 {
return nil, httperrors.NewConflictError("guest already bound to a snapshot policy")
}
// 若主机下任意磁盘已绑定快照策略,则主机不能再绑定主机快照策略
disks, err := self.GetDisks()
if err != nil {
return nil, errors.Wrap(err, "GetDisks")
}
for _, d := range disks {
diskCnt, err := SnapshotPolicyResourceManager.GetBindingCount(d.Id, api.SNAPSHOT_POLICY_TYPE_DISK)
if err != nil {
return nil, errors.Wrap(err, "GetBindingCount for disk")
}
if diskCnt > 0 {
return nil, httperrors.NewConflictError("guest has disk %s bound to snapshot policy, guest cannot bind server snapshot policy", d.Name)
}
}
sr := &SSnapshotPolicyResource{}
sr.SetModelManager(SnapshotPolicyResourceManager, sr)
sr.SnapshotpolicyId = sp.Id
sr.ResourceId = self.Id
sr.ResourceType = api.SNAPSHOT_POLICY_TYPE_SERVER
if err := SnapshotPolicyResourceManager.TableSpec().Insert(ctx, sr); err != nil {
return nil, errors.Wrap(err, "Insert")
}
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_BIND, input, userCred, true)
return nil, nil
}
// 设置主机快照策略
// 可覆盖当前主机绑定的快照策略,若主机下任意磁盘已绑定快照策略,则自动解除磁盘快照策略
func (self *SGuest) PerformSetSnapshotpolicy(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, input *api.ServerSnapshotpolicyInput) (jsonutils.JSONObject, error) {
if len(input.SnapshotpolicyId) == 0 {
return nil, httperrors.NewMissingParameterError("snapshotpolicy_id")
}
spObj, err := validators.ValidateModel(ctx, userCred, SnapshotPolicyManager, &input.SnapshotpolicyId)
if err != nil {
return nil, err
}
sp := spObj.(*SSnapshotPolicy)
if sp.Type != api.SNAPSHOT_POLICY_TYPE_SERVER {
return nil, httperrors.NewBadRequestError("The snapshot policy %s is not a server snapshot policy", sp.Name)
}
if err := SnapshotPolicyResourceManager.RemoveByResource(self.Id, api.SNAPSHOT_POLICY_TYPE_SERVER); err != nil {
return nil, errors.Wrap(err, "RemoveByResource")
}
// 若主机下任意磁盘已绑定快照策略,则主机不能再绑定主机快照策略
disks, err := self.GetDisks()
if err != nil {
return nil, errors.Wrap(err, "GetDisks")
}
for _, d := range disks {
if err := SnapshotPolicyResourceManager.RemoveByResource(d.Id, api.SNAPSHOT_POLICY_TYPE_DISK); err != nil {
return nil, errors.Wrap(err, "RemoveByResource")
}
}
sr := &SSnapshotPolicyResource{}
sr.SetModelManager(SnapshotPolicyResourceManager, sr)
sr.SnapshotpolicyId = sp.Id
sr.ResourceId = self.Id
sr.ResourceType = api.SNAPSHOT_POLICY_TYPE_SERVER
if err := SnapshotPolicyResourceManager.TableSpec().Insert(ctx, sr); err != nil {
return nil, errors.Wrap(err, "Insert")
}
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_UPDATE, input, userCred, true)
return nil, nil
}
func (self *SGuest) checkGroups(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject,
data jsonutils.JSONObject) (sets.String, error) {
groupIdArr := jsonutils.GetArrayOfPrefix(data, "group")
if len(groupIdArr) == 0 {
return nil, httperrors.NewMissingParameterError("group.0 group.1 ... ")
}
groupIdSet := sets.NewString()
for i := range groupIdArr {
groupIdStr, _ := groupIdArr[i].GetString()
model, err := GroupManager.FetchByIdOrName(ctx, userCred, groupIdStr)
if err == sql.ErrNoRows {
return nil, httperrors.NewInputParameterError("no such group %s", groupIdStr)
}
if err != nil {
return nil, errors.Wrapf(err, "fail to fetch group by id or name %s", groupIdStr)
}
group := model.(*SGroup)
if group.ProjectId != self.ProjectId {
return nil, httperrors.NewForbiddenError("group and guest should belong to same project")
}
if group.Enabled.IsFalse() {
return nil, httperrors.NewForbiddenError("cannot bind or unbind disabled instance group")
}
groupIdSet.Insert(group.GetId())
}
return groupIdSet, nil
}
// 公网Ip转Eip
// 要求虚拟机有公网IP,并且虚拟机状态为running 或 ready
// 目前仅支持阿里云和腾讯云
func (self *SGuest) PerformPublicipToEip(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.GuestPublicipToEipInput) (jsonutils.JSONObject, error) {
publicip, err := self.GetPublicIp()
if err != nil {
return nil, httperrors.NewGeneralError(errors.Wrap(err, "GetPublicIp"))
}
if publicip == nil {
return nil, httperrors.NewInputParameterError("The guest %s does not have any public IP", self.Name)
}
if !utils.IsInStringArray(self.Status, []string{api.VM_READY, api.VM_RUNNING}) {
return nil, httperrors.NewUnsupportOperationError("The guest status need be %s or %s, current is %s", api.VM_READY, api.VM_RUNNING, self.Status)
}
driver, err := self.GetDriver()
if err != nil {
return nil, err
}
if !driver.IsSupportPublicipToEip() {
return nil, httperrors.NewUnsupportOperationError("guest hypervisor %s does not support public IP to EIP conversion", self.Hypervisor)
}
return nil, self.StartPublicipToEipTask(ctx, userCred, input.AutoStart, "")
}
func (self *SGuest) StartPublicipToEipTask(ctx context.Context, userCred mcclient.TokenCredential, autoStart bool, parentTaskId string) error {
data := jsonutils.NewDict()
data.Set("auto_start", jsonutils.NewBool(autoStart))
task, err := taskman.TaskManager.NewTask(ctx, "GuestPublicipToEipTask", self, userCred, data, parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
self.SetStatus(ctx, userCred, api.VM_START_EIP_CONVERT, "")
task.ScheduleRun(nil)
return nil
}
func (self *SGuest) SetAutoRenew(autoRenew bool) error {
_, err := db.Update(self, func() error {
self.AutoRenew = autoRenew
return nil
})
return err
}
// 设置自动续费
// 要求虚拟机状态为running 或 ready
// 要求虚拟机计费类型为包年包月(预付费)
func (self *SGuest) PerformSetAutoRenew(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.GuestAutoRenewInput) (jsonutils.JSONObject, error) {
if !utils.IsInStringArray(self.Status, []string{api.VM_READY, api.VM_RUNNING}) {
return nil, httperrors.NewUnsupportOperationError("The guest status need be %s or %s, current is %s", api.VM_READY, api.VM_RUNNING, self.Status)
}
if self.BillingType != billing_api.BILLING_TYPE_PREPAID {
return nil, httperrors.NewUnsupportOperationError("Only %s guest support this operation", billing_api.BILLING_TYPE_PREPAID)
}
if len(input.Duration) == 0 {
input.Duration = "1M"
}
_, err := billing.ParseBillingCycle(input.Duration)
if err != nil {
return nil, httperrors.NewInputParameterError("invalid duration %s: %s", input.Duration, err)
}
if self.AutoRenew == input.AutoRenew {
return nil, nil
}
driver, err := self.GetDriver()
if err != nil {
return nil, err
}
if !driver.IsSupportSetAutoRenew() {
err := self.SetAutoRenew(input.AutoRenew)
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
logclient.AddSimpleActionLog(self, logclient.ACT_SET_AUTO_RENEW, jsonutils.Marshal(input), userCred, true)
return nil, nil
}
return nil, self.StartSetAutoRenewTask(ctx, userCred, input, "")
}
func (self *SGuest) StartSetAutoRenewTask(ctx context.Context, userCred mcclient.TokenCredential, input api.GuestAutoRenewInput, parentTaskId string) error {
data := jsonutils.NewDict()
data.Set("auto_renew", jsonutils.NewBool(input.AutoRenew))
data.Set("duration", jsonutils.NewString(input.Duration))
task, err := taskman.TaskManager.NewTask(ctx, "GuestSetAutoRenewTask", self, userCred, data, parentTaskId, "", nil)
if err != nil {
return errors.Wrap(err, "NewTask")
}
self.SetStatus(ctx, userCred, api.VM_SET_AUTO_RENEW, "")
task.ScheduleRun(nil)
return nil
}
func (self *SGuest) PerformRemoteUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerRemoteUpdateInput) (jsonutils.JSONObject, error) {
err := self.StartRemoteUpdateTask(ctx, userCred, (input.ReplaceTags != nil && *input.ReplaceTags), "")
if err != nil {
return nil, errors.Wrap(err, "StartRemoteUpdateTask")
}
return nil, nil
}
func (self *SGuest) PerformOpenForward(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
req, err := guestdriver_types.NewOpenForwardRequestFromJSON(ctx, data)
if err != nil {
return nil, err
}
for _, nicDesc := range self.fetchNICShortDesc(ctx) {
if nicDesc.VpcId != api.DEFAULT_VPC_ID {
if req.Addr == "" {
req.Addr = nicDesc.IpAddr
}
req.NetworkId = nicDesc.NetworkId
}
}
if req.NetworkId == "" {
return nil, httperrors.NewInputParameterError("guest has no vpc ip")
}
driver, err := self.GetDriver()
if err != nil {
return nil, errors.Wrapf(err, "GetDriver")
}
resp, err := driver.RequestOpenForward(ctx, userCred, self, req)
if err != nil {
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_WEBSSH, err, userCred, false)
return nil, httperrors.NewGeneralError(err)
}
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_WEBSSH, resp, userCred, true)
return resp.JSON(), nil
}
func (self *SGuest) PerformCloseForward(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
req, err := guestdriver_types.NewCloseForwardRequestFromJSON(ctx, data)
if err != nil {
return nil, err
}
for _, nicDesc := range self.fetchNICShortDesc(ctx) {
if nicDesc.VpcId != api.DEFAULT_VPC_ID {
req.NetworkId = nicDesc.NetworkId
}
}
if req.NetworkId == "" {
return nil, httperrors.NewInputParameterError("guest has no vpc ip")
}
driver, err := self.GetDriver()
if err != nil {
return nil, err
}
resp, err := driver.RequestCloseForward(ctx, userCred, self, req)
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
return resp.JSON(), nil
}
func (self *SGuest) PerformListForward(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
req, err := guestdriver_types.NewListForwardRequestFromJSON(ctx, data)
if err != nil {
return nil, err
}
for _, nicDesc := range self.fetchNICShortDesc(ctx) {
if nicDesc.VpcId != api.DEFAULT_VPC_ID {
if req.Addr == "" {
req.Addr = nicDesc.IpAddr
}
req.NetworkId = nicDesc.NetworkId
}
}
if req.NetworkId == "" {
return nil, httperrors.NewInputParameterError("guest has no vpc ip")
}
driver, err := self.GetDriver()
if err != nil {
return nil, err
}
resp, err := driver.RequestListForward(ctx, userCred, self, req)
if err != nil {
return nil, httperrors.NewGeneralError(err)
}
return resp.JSON(), nil
}
// 更改存储
func (self *SGuest) PerformChangeStorage(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.ServerChangeStorageInput) (*api.ServerChangeStorageInput, error) {
// validate input
if input.TargetStorageId == "" {
return nil, httperrors.NewNotEmptyError("Storage id is empty")
}
// validate storage
storageObj, err := StorageManager.FetchByIdOrName(ctx, userCred, input.TargetStorageId)
if err != nil {
return nil, errors.Wrapf(err, "Found storage by %s", input.TargetStorageId)
}
storage := storageObj.(*SStorage)
input.TargetStorageId = storage.GetId()
// validate disk
disks, err := self.GetDisks()
if err != nil {
return nil, errors.Wrapf(err, "Get server %s disks", self.GetName())
}
var changeDisks = []string{}
for _, disk := range disks {
if disk.StorageId != input.TargetStorageId {
changeDisks = append(changeDisks, disk.Id)
}
}
// driver validate
drv, err := self.GetDriver()
if err != nil {
return nil, err
}
if err := drv.ValidateChangeDiskStorage(ctx, userCred, self, input.TargetStorageId); err != nil {
return nil, err
}
return nil, self.StartGuestChangeStorageTask(ctx, userCred, input, changeDisks)
}
func (self *SGuest) StartGuestChangeStorageTask(ctx context.Context, userCred mcclient.TokenCredential, input *api.ServerChangeStorageInput, disks []string) error {
params := api.ServerChangeStorageInternalInput{
ServerChangeStorageInput: *input,
Disks: disks,
DiskCount: len(disks),
GuestRunning: self.Status == api.VM_RUNNING,
}
reason := fmt.Sprintf("Change guest disks storage to %s", input.TargetStorageId)
self.SetStatus(ctx, userCred, api.VM_DISK_CHANGE_STORAGE, reason)
if task, err := taskman.TaskManager.NewTask(
ctx, "GuestChangeDisksStorageTask", self, userCred, jsonutils.Marshal(params).(*jsonutils.JSONDict),
"", "", nil); err != nil {
return err
} else {
task.ScheduleRun(nil)
}
return nil
}
func (self *SGuest) PerformChangeDiskDriver(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.ServerChangeDiskDriverInput) (jsonutils.JSONObject, error) {
if self.Status != api.VM_READY {
return nil, httperrors.NewInvalidStatusError("can't change disk driver in guest status %s", self.Status)
}
gd := self.GetGuestDisk(input.DiskId)
if gd == nil {
return nil, httperrors.NewBadRequestError("failed get guest disk by disk id %s", input.DiskId)
}
var driverChanged = false
if input.Driver != "" {
if input.Driver != gd.Driver {
driverChanged = true
}
if !utils.IsInStringArray(input.Driver, []string{api.DISK_DRIVER_VIRTIO, api.DISK_DRIVER_PVSCSI, api.DISK_DRIVER_IDE, api.DISK_DRIVER_SCSI}) {
return nil, httperrors.NewInputParameterError("unknown driver %s", input.Driver)
}
}
if input.CacheMode != "" && gd.CacheMode != input.CacheMode {
if input.CacheMode != "none" {
input.AioMode = "threads"
}
if !utils.IsInStringArray(input.CacheMode, []string{api.DISK_CACHE_MODE_WRITETHROGH, api.DISK_CACHE_MODE_DIRECTSYNC, api.DISK_CACHE_MODE_WRITEBACK, api.DISK_CACHE_MODE_NONE}) {
return nil, httperrors.NewInputParameterError("unknown cache_mode %s", input.CacheMode)
}
}
if input.AioMode != "" && gd.AioMode != input.AioMode {
if !utils.IsInStringArray(input.AioMode, []string{api.DISK_AIO_MODE_NATIVE, api.DISK_AIO_MOD_THREADS}) {
return nil, httperrors.NewInputParameterError("unknown aio_mode %s", input.AioMode)
}
cacheMode := gd.CacheMode
if input.CacheMode != "" {
cacheMode = input.CacheMode
}
if input.AioMode == "native" && cacheMode != "none" {
return nil, httperrors.NewBadRequestError("AIO mode %s with cache mode %s is not supported", input.AioMode, cacheMode)
}
}
_, err := db.Update(gd, func() error {
if input.Driver != "" {
gd.Driver = input.Driver
}
if input.AioMode != "" {
gd.AioMode = input.AioMode
}
if input.CacheMode != "" {
gd.CacheMode = input.CacheMode
}
return nil
})
if err != nil {
return nil, errors.Wrap(err, "failed update disk driver")
}
if driverChanged && self.Bios == api.VM_BOOT_MODE_UEFI {
drv, err := self.GetDriver()
if err != nil {
return nil, err
}
err = drv.RequestResetUefiFirmwareVars(ctx, userCred, self)
if err != nil {
return nil, errors.Wrap(err, "failed reset uefi fw vars")
}
}
return nil, nil
}
func (self *SGuest) PerformChangeDiskStorage(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.ServerChangeDiskStorageInput) (*api.ServerChangeDiskStorageInput, error) {
// validate input
if input.DiskId == "" {
return nil, httperrors.NewNotEmptyError("Disk id is empty")
}
if input.TargetStorageId == "" {
return nil, httperrors.NewNotEmptyError("Storage id is empty")
}
// validate disk
disks, err := self.GetDisks()
if err != nil {
return nil, errors.Wrapf(err, "Get server %s disks", self.GetName())
}
var srcDisk *SDisk
for _, disk := range disks {
if input.DiskId == disk.GetId() || input.DiskId == disk.GetName() {
srcDisk = &disk
input.DiskId = disk.GetId()
break
}
}
if srcDisk == nil {
return nil, httperrors.NewNotFoundError("Disk %s not found on server %s", input.DiskId, self.GetName())
}
// validate storage
storageObj, err := StorageManager.FetchByIdOrName(ctx, userCred, input.TargetStorageId)
if err != nil {
return nil, errors.Wrapf(err, "Found storage by %s", input.TargetStorageId)
}
storage := storageObj.(*SStorage)
input.TargetStorageId = storage.GetId()
// driver validate
drv, err := self.GetDriver()
if err != nil {
return nil, err
}
if err := drv.ValidateChangeDiskStorage(ctx, userCred, self, input.TargetStorageId); err != nil {
return nil, err
}
{
copyInput := api.ServerCopyDiskToStorageInput{
KeepOriginDisk: input.KeepOriginDisk,
GuestRunning: self.Status == api.VM_RUNNING,
}
err := self.CopyDiskToStorage(ctx, userCred, srcDisk, storage, copyInput, "")
if err != nil {
return nil, errors.Wrap(err, "CopyDiskToStorage")
}
}
return nil, nil
}
func (guest *SGuest) CopyDiskToStorage(ctx context.Context, userCred mcclient.TokenCredential, srcDisk *SDisk, storage *SStorage, input api.ServerCopyDiskToStorageInput, parentTaskId string) error {
diskConf := &api.DiskConfig{
Index: -1,
ImageId: srcDisk.TemplateId,
SizeMb: srcDisk.DiskSize,
Fs: srcDisk.FsFormat,
DiskType: srcDisk.DiskType,
}
targetDisk, err := guest.CreateDiskOnStorage(ctx, userCred, storage, diskConf, nil, true, true)
if err != nil {
return errors.Wrapf(err, "Create target disk on storage %s", storage.GetName())
}
{
err := db.InheritFromTo(ctx, userCred, srcDisk, targetDisk)
if err != nil {
return errors.Wrapf(err, "Inherit class metadata from src %s to target %s", srcDisk.GetName(), targetDisk.GetName())
}
}
internalInput := &api.ServerChangeDiskStorageInternalInput{
ServerChangeDiskStorageInput: api.ServerChangeDiskStorageInput{
DiskId: srcDisk.Id,
TargetStorageId: storage.Id,
KeepOriginDisk: input.KeepOriginDisk,
},
StorageId: srcDisk.StorageId,
TargetDiskId: targetDisk.GetId(),
GuestRunning: input.GuestRunning,
CloneDiskCount: input.CloneDiskCount,
CompletedDiskCount: input.CompletedDiskCount,
}
{
err := guest.StartChangeDiskStorageTask(ctx, userCred, internalInput, parentTaskId)
if err != nil {
return errors.Wrap(err, "StartChangeDiskStorageTask")
}
}
return nil
}
func (self *SGuest) StartChangeDiskStorageTask(ctx context.Context, userCred mcclient.TokenCredential, input *api.ServerChangeDiskStorageInternalInput, parentTaskId string) error {
reason := fmt.Sprintf("Change disk %s to storage %s", input.DiskId, input.TargetStorageId)
self.SetStatus(ctx, userCred, api.VM_DISK_CHANGE_STORAGE, reason)
driver, err := self.GetDriver()
if err != nil {
return err
}
return driver.StartChangeDiskStorageTask(self, ctx, userCred, input, parentTaskId)
}
func (self *SGuest) startSwitchToClonedDisk(ctx context.Context, userCred mcclient.TokenCredential, taskId string) error {
task := taskman.TaskManager.FetchTaskById(taskId)
if task == nil {
return errors.Errorf("no task %s found", taskId)
}
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
log.Infof("guest %s start switch to cloned disk task", self.Id)
params := jsonutils.NewDict()
params.Set("block_jobs_ready", jsonutils.JSONTrue)
return params, nil
})
return nil
}
// 设置启动顺序
func (self *SGuest) PerformSetBootIndex(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.ServerSetBootIndexInput) (jsonutils.JSONObject, error) {
gds, err := self.GetGuestDisks()
if err != nil {
return nil, err
}
diskBootIndexes := map[int8]int8{}
for i := 0; i < len(gds); i++ {
diskBootIndexes[gds[i].Index] = gds[i].BootIndex
}
gcs, err := self.getCdroms()
if err != nil {
return nil, err
}
cdromBootIndexes := map[int]int8{}
for i := 0; i < len(gcs); i++ {
cdromBootIndexes[gcs[i].Ordinal] = gcs[i].BootIndex
}
for sDiskIndex, bootIndex := range input.Disks {
iDiskIndex, err := strconv.Atoi(sDiskIndex)
if err != nil {
return nil, httperrors.NewInputParameterError("failed to parse disk index %s", sDiskIndex)
} else if iDiskIndex > 127 {
return nil, httperrors.NewInputParameterError("disk inex %s is exceed 127", sDiskIndex)
}
diskIndex := int8(iDiskIndex)
if _, ok := diskBootIndexes[diskIndex]; !ok {
return nil, httperrors.NewBadRequestError("disk has no index %d", diskIndex)
}
diskBootIndexes[diskIndex] = bootIndex
}
for sCdromOrdinal, bootIndex := range input.Cdroms {
cdromOrdinal, err := strconv.Atoi(sCdromOrdinal)
if err != nil {
return nil, httperrors.NewInputParameterError("failed to parse cdrom ordinal %s", sCdromOrdinal)
}
if _, ok := cdromBootIndexes[cdromOrdinal]; !ok {
return nil, httperrors.NewBadRequestError("cdrom has no ordinal %d", cdromOrdinal)
}
cdromBootIndexes[cdromOrdinal] = bootIndex
}
bm := bitmap.NewBitMap(128)
for diskIndex, bootIndex := range diskBootIndexes {
if bootIndex < 0 {
continue
}
if bm.Has(int64(bootIndex)) {
return nil, httperrors.NewBadRequestError("disk index %d boot index %d is duplicated", diskIndex, bootIndex)
} else {
bm.Set(int64(bootIndex))
}
}
for cdromOrdinal, bootIndex := range cdromBootIndexes {
if bootIndex < 0 {
continue
}
if bm.Has(int64(bootIndex)) {
return nil, httperrors.NewBadRequestError("cdrom ordianl %d boot index %d is duplicated", cdromOrdinal, bootIndex)
} else {
bm.Set(int64(bootIndex))
}
}
for i := 0; i < len(gds); i++ {
if gds[i].BootIndex != diskBootIndexes[gds[i].Index] {
if err := gds[i].SetBootIndex(diskBootIndexes[gds[i].Index]); err != nil {
log.Errorf("gds[i].SetBootIndex: %s", err)
return nil, err
}
}
}
for i := 0; i < len(gcs); i++ {
if gcs[i].BootIndex != cdromBootIndexes[gcs[i].Ordinal] {
if err := gcs[i].SetBootIndex(cdromBootIndexes[gcs[i].Ordinal]); err != nil {
log.Errorf("gcs[i].SetBootIndex: %s", err)
return nil, err
}
}
}
if self.Bios == api.VM_BOOT_MODE_UEFI {
data := jsonutils.NewDict()
data.Set("set_uefi_boot_order", jsonutils.JSONTrue)
if err := self.startSyncTask(ctx, userCred, false, "", data); err != nil {
return nil, err
}
}
return nil, nil
}
// 探测透传设备
func (self *SGuest) PerformProbeIsolatedDevices(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
host, err := self.GetHost()
if err != nil {
return nil, errors.Wrap(err, "GetHost")
}
driver, err := host.GetHostDriver()
if err != nil {
return nil, errors.Wrapf(err, "GetHostDriver")
}
hostDevs, err := driver.RequestProbeIsolatedDevices(ctx, userCred, host, data)
if err != nil {
return nil, errors.Wrap(err, "RequestProbeIsolatedDevices")
}
objs, err := hostDevs.GetArray()
if err != nil {
return nil, errors.Wrapf(err, "GetArray from %q", hostDevs)
}
devs := make([]*SIsolatedDevice, 0)
for _, obj := range objs {
id, err := obj.GetString("id")
if err != nil {
return nil, errors.Wrapf(err, "device %s", obj)
}
devObj, err := IsolatedDeviceManager.FetchById(id)
if err != nil {
return nil, errors.Wrapf(err, "FetchById %q", id)
}
dev := devObj.(*SIsolatedDevice)
if !dev.IsFull() {
devs = append(devs, dev)
}
}
return jsonutils.Marshal(devs), nil
}
func (self *SGuest) PerformCpuset(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *api.ServerCPUSetInput) (jsonutils.JSONObject, error) {
host, err := self.GetHost()
if err != nil {
return nil, errors.Wrap(err, "get host model")
}
allCores, err := host.getHostLogicalCores()
if err != nil {
return nil, err
}
if !sets.NewInt(allCores...).HasAll(data.CPUS...) {
return nil, httperrors.NewInputParameterError("Host cores %v not contains input %v", allCores, data.CPUS)
}
hostReservedCpus, err := host.GetReservedCpus()
if err != nil {
return nil, errors.Wrap(err, "host get reserved cpus")
}
for i := range data.CPUS {
if hostReservedCpus.Contains(data.CPUS[i]) {
return nil, httperrors.NewBadRequestError("request cpu %d has been reserved", data.CPUS[i])
}
}
pinnedMap, err := host.GetPinnedCpusetCores(ctx, userCred, []string{self.Id})
if err != nil {
return nil, errors.Wrap(err, "Get host pinned cpu cores")
}
if pinnedMap != nil {
for i := range data.CPUS {
if pinnedMap.Contains(data.CPUS[i]) {
return nil, httperrors.NewBadRequestError("request cpu %d has been set by other guests", data.CPUS[i])
}
}
}
if err := self.SetMetadata(ctx, api.VM_METADATA_CGROUP_CPUSET, data, userCred); err != nil {
return nil, errors.Wrap(err, "set metadata")
}
if err := host.updateHostReservedCpus(ctx, userCred); err != nil {
return nil, errors.Wrap(err, "updateHostReservedCpus")
}
return nil, self.StartGuestCPUSetTask(ctx, userCred, data)
}
func (self *SGuest) StartGuestCPUSetTask(ctx context.Context, userCred mcclient.TokenCredential, input *api.ServerCPUSetInput) error {
task, err := taskman.TaskManager.NewTask(ctx, "GuestCPUSetTask", self, userCred, jsonutils.Marshal(input).(*jsonutils.JSONDict), "", "")
if err != nil {
return errors.Wrap(err, "New GuestCPUSetTask")
}
return task.ScheduleRun(nil)
}
func (self *SGuest) PerformCpusetRemove(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *api.ServerCPUSetRemoveInput) (*api.ServerCPUSetRemoveResp, error) {
if err := self.RemoveMetadata(ctx, api.VM_METADATA_CGROUP_CPUSET, userCred); err != nil {
return nil, errors.Wrapf(err, "remove metadata %q", api.VM_METADATA_CGROUP_CPUSET)
}
host, err := self.GetHost()
if err != nil {
return nil, errors.Wrap(err, "get host model")
}
// TODO: maybe change to async task
db.OpsLog.LogEvent(self, db.ACT_GUEST_CPUSET_REMOVE, nil, userCred)
resp := new(api.ServerCPUSetRemoveResp)
drv, err := self.GetDriver()
if err != nil {
return nil, err
}
if err := drv.RequestCPUSetRemove(ctx, userCred, host, self, data); err != nil {
db.OpsLog.LogEvent(self, db.ACT_GUEST_CPUSET_REMOVE_FAIL, err, userCred)
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_VM_CPUSET_REMOVE, data, userCred, false)
resp.Error = err.Error()
} else {
db.OpsLog.LogEvent(self, db.ACT_GUEST_CPUSET_REMOVE, nil, userCred)
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_VM_CPUSET_REMOVE, data, userCred, true)
resp.Done = true
}
return resp, nil
}
func (self *SGuest) getPinnedCpusetCores(ctx context.Context, userCred mcclient.TokenCredential) ([]int, error) {
obj := self.GetMetadataJson(ctx, api.VM_METADATA_CGROUP_CPUSET, userCred)
if obj == nil {
return nil, nil
}
pinnedInput := new(api.ServerCPUSetInput)
if err := obj.Unmarshal(pinnedInput); err != nil {
return nil, errors.Wrap(err, "Unmarshal to ServerCPUSetInput")
}
return pinnedInput.CPUS, nil
}
func (self *SGuest) GetDetailsCpusetCores(ctx context.Context, userCred mcclient.TokenCredential, input *api.ServerGetCPUSetCoresInput) (*api.ServerGetCPUSetCoresResp, error) {
host, err := self.GetHost()
if err != nil {
return nil, err
}
allCores, err := host.getHostLogicalCores()
if err != nil {
return nil, err
}
usedMap, err := host.GetPinnedCpusetCores(ctx, userCred, nil)
if err != nil {
return nil, err
}
resp := &api.ServerGetCPUSetCoresResp{
HostCores: allCores,
}
if usedMap != nil {
resp.HostUsedCores = usedMap.ToSlice()
}
// fetch cpuset pinned
pinned, err := self.getPinnedCpusetCores(ctx, userCred)
if err != nil {
log.Errorf("getPinnedCpusetCores error: %v", err)
} else {
resp.PinnedCores = pinned
}
return resp, nil
}
func (self *SGuest) GetDetailsNumaInfo(ctx context.Context, userCred mcclient.TokenCredential, _ *api.ServerGetNumaInfoInput) (*api.ServerGetNumaInfoResp, error) {
ret := new(api.ServerGetNumaInfoResp)
devs, _ := self.GetGuestIsolatedDevices()
if len(devs) > 0 {
ret.IsolatedDevicesNumaNode = make([]int8, 0)
for i := range devs {
dev := devs[i].GetIsolatedDevice()
if dev.NumaNode > 0 {
ret.IsolatedDevicesNumaNode = append(ret.IsolatedDevicesNumaNode, dev.NumaNode)
}
}
}
ret.CpuNumaPin = self.CpuNumaPin
return ret, nil
}
func (self *SGuest) GetDetailsHardwareInfo(ctx context.Context, userCred mcclient.TokenCredential, _ *api.ServerGetHardwareInfoInput) (*api.ServerGetHardwareInfoResp, error) {
host, err := self.GetHost()
if err != nil {
return nil, errors.Wrap(err, "GetHost")
}
hostSpecObj := host.GetSpec(false)
hostSpec := new(api.HostSpec)
if hostSpecObj != nil {
if err := hostSpecObj.Unmarshal(hostSpec); err != nil {
return nil, errors.Wrap(err, "unmarshal host spec")
}
}
motherboardInfo := &api.ServerHardwareInfoMotherboard{}
if host.SysInfo != nil && host.SysInfo.Contains("motherboard_info") {
if err := host.SysInfo.Unmarshal(motherboardInfo, "motherboard_info"); err != nil {
return nil, errors.Wrapf(err, "unmarshal motherboard_info from host system info")
}
}
cpuInfo := &api.ServerHardwareInfoCPU{
Model: host.CpuDesc,
Count: self.VcpuCount,
}
memInfo := &api.ServerHardwareInfoMemory{
SizeMB: self.VmemSize,
}
// fill disks info
diskInfos := make([]*api.ServerHardwareInfoDisk, 0)
disks, err := self.GetDisks()
if err != nil {
return nil, errors.Wrap(err, "get disks")
}
for _, disk := range disks {
storage, err := disk.GetStorage()
if err != nil {
return nil, errors.Wrapf(err, "get disk %s storage", disk.GetId())
}
hdInfo, err := storage.GetDetailsHardwareInfo(ctx, userCred, nil)
if err != nil {
return nil, errors.Wrapf(err, "get storage %s hardware info", storage.GetId())
}
info := &api.ServerHardwareInfoDisk{
Id: disk.GetId(),
StorageId: storage.GetId(),
SizeMB: disk.DiskSize,
Bandwidth: hdInfo.Bandwidth,
}
if hdInfo.Vendor != nil {
info.Model = *hdInfo.Vendor
}
if hdInfo.Model != nil {
info.Model = fmt.Sprintf("%s %s", info.Model, *hdInfo.Model)
}
diskInfos = append(diskInfos, info)
}
// fill GPU info
devs, err := self.GetGuestIsolatedDevices()
if err != nil {
return nil, errors.Wrap(err, "get isolated devices")
}
gpuInfos := make([]*api.ServerHardwareInfoGPU, 0)
for i := range devs {
dev := devs[i].GetIsolatedDevice()
if !dev.IsGPU() {
continue
}
info := &api.ServerHardwareInfoGPU{
Id: dev.GetId(),
Model: dev.Model,
}
if dev.PcieInfo != nil {
info.PCIEInfo = dev.PcieInfo
}
modelObj, _ := IsolatedDeviceModelManager.GetByDevModel(dev.Model)
if modelObj != nil {
hdInfo, err := modelObj.GetDetailsHardwareInfo(ctx, userCred, nil)
if err != nil {
return nil, errors.Wrapf(err, "get device model %s hardware info", modelObj.GetId())
}
info.IsolatedDeviceModelHardwareInfo = hdInfo
}
gpuInfos = append(gpuInfos, info)
}
result := &api.ServerGetHardwareInfoResp{
Motherboard: motherboardInfo,
CPU: cpuInfo,
Memory: memInfo,
Disks: diskInfos,
GPUs: gpuInfos,
}
return result, nil
}
func (self *SGuest) PerformCalculateRecordChecksum(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
checksum, err := db.CalculateModelChecksum(self)
if err != nil {
return nil, errors.Wrap(err, "CalculateModelChecksum")
}
return jsonutils.Marshal(map[string]string{
"checksum": checksum,
}), nil
}
func (self *SGuest) PerformEnableMemclean(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
return nil, self.SetMetadata(ctx, api.VM_METADATA_ENABLE_MEMCLEAN, "true", userCred)
}
func (self *SGuest) PerformSetTpm(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
enableTpm := jsonutils.QueryBoolean(data, api.VM_METADATA_ENABLE_TPM, false)
if enableTpm {
return nil, self.SetMetadata(ctx, api.VM_METADATA_ENABLE_TPM, enableTpm, userCred)
} else {
return nil, self.RemoveMetadata(ctx, api.VM_METADATA_ENABLE_TPM, userCred)
}
}
func (self *SGuest) PerformSetCpuNumaPin(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if !data.Contains("cpu_numa_pin") {
return nil, httperrors.NewMissingParameterError("cpu_numa_pin")
}
cpuNumaPin := make([]api.SCpuNumaPin, 0)
err := data.Unmarshal(&cpuNumaPin, "cpu_numa_pin")
if err != nil {
return nil, httperrors.NewInputParameterError("failed unmarshal cpu_numa_pin %s", err)
} else {
if err = self.SetCpuNumaPin(ctx, userCred, nil, cpuNumaPin); err != nil {
return nil, errors.Wrap(err, "failed set cpu numa pin")
}
}
return nil, nil
}
// 设置操作系统信息
func (self *SGuest) PerformSetOsInfo(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerSetOSInfoInput) (jsonutils.JSONObject, error) {
drv, err := self.GetDriver()
if err != nil {
return nil, err
}
if err := drv.ValidateSetOSInfo(ctx, userCred, self, &input); err != nil {
return nil, err
}
if input.Type != "" {
if _, err := db.Update(self, func() error {
self.OsType = input.Type
return nil
}); err != nil {
return nil, errors.Wrapf(err, "update os_type")
}
}
for k, v := range map[string]string{
api.VM_METADATA_OS_NAME: input.Type,
api.VM_METADATA_OS_VERSION: input.Version,
api.VM_METADATA_OS_DISTRO: input.Distribution,
api.VM_METADATA_OS_ARCH: input.Arch,
} {
if len(v) == 0 {
continue
}
if err := self.SetMetadata(ctx, k, v, userCred); err != nil {
return nil, errors.Wrapf(err, "set metadata %s to %s", k, v)
}
}
return nil, nil
}
// 同步操作系统信息
func (self *SGuest) PerformSyncOsInfo(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
drv, err := self.GetDriver()
if err != nil {
return nil, err
}
if err := drv.ValidateSyncOSInfo(ctx, userCred, self); err != nil {
return nil, err
}
if self.Status == api.VM_READY {
// start guest deploy task to sync os info
return nil, self.StartGuestDeployTask(ctx, userCred, nil, "deploy", "")
} else {
res, err := self.PerformQgaPing(ctx, userCred, nil, nil)
if err != nil || res.Contains("ping_error") {
return nil, httperrors.NewBadRequestError("qga ping failed is qga running?")
}
// try qga get os info
return nil, self.startQgaSyncOsInfoTask(ctx, userCred, "")
}
}
func (g *SGuest) PerformSetRootDiskMatcher(ctx context.Context, userCred mcclient.TokenCredential, _ jsonutils.JSONObject, data *api.BaremetalRootDiskMatcher) (jsonutils.JSONObject, error) {
if g.GetHypervisor() != api.HYPERVISOR_BAREMETAL {
return nil, httperrors.NewNotAcceptableError("only %s support for setting root disk matcher", api.HYPERVISOR_BAREMETAL)
}
if err := baremetal.ValidateRootDiskMatcher(data); err != nil {
return nil, err
}
if err := g.SetMetadata(ctx, api.BAREMETAL_SERVER_METATA_ROOT_DISK_MATCHER, jsonutils.Marshal(data), userCred); err != nil {
return nil, errors.Wrapf(err, "set %s", api.BAREMETAL_SERVER_METATA_ROOT_DISK_MATCHER)
}
return nil, nil
}
func (g *SGuest) PerformChangeBillingType(ctx context.Context, userCred mcclient.TokenCredential, _ jsonutils.JSONObject, input *api.ServerChangeBillingTypeInput) (jsonutils.JSONObject, error) {
if !utils.IsInStringArray(g.Status, []string{api.VM_RUNNING, api.VM_READY}) {
return nil, httperrors.NewServerStatusError("Cannot change guest billing type in status %s", g.Status)
}
if len(input.BillingType) == 0 {
return nil, httperrors.NewMissingParameterError("billing_type")
}
if !utils.IsInStringArray(string(input.BillingType), []string{string(billing_api.BILLING_TYPE_POSTPAID), string(billing_api.BILLING_TYPE_PREPAID)}) {
return nil, httperrors.NewInputParameterError("invalid billing_type %s", input.BillingType)
}
if g.BillingType == input.BillingType {
return nil, nil
}
return nil, g.StartChangeBillingTypeTask(ctx, userCred, "")
}
func (self *SGuest) StartChangeBillingTypeTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
self.SetStatus(ctx, userCred, apis.STATUS_CHANGE_BILLING_TYPE, "")
kwargs := jsonutils.NewDict()
task, err := taskman.TaskManager.NewTask(ctx, "GuestChangeBillingTypeTask", self, userCred, kwargs, parentTaskId, "", nil)
if err != nil {
return err
}
return task.ScheduleRun(nil)
}
func (self *SGuest) PerformDisableAutoMergeSnapshots(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
disableAutoMergeSnapshot := jsonutils.QueryBoolean(data, "disable_auto_merge_snapshot", false)
return nil, self.SetMetadata(ctx, api.VM_METADATA_DISABLE_AUTO_MERGE_SNAPSHOT, disableAutoMergeSnapshot, userCred)
}
func (self *SGuest) PerformSetKickstart(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.KickstartConfig) (jsonutils.JSONObject, error) {
// Check if kickstart has already been completed
currentFlag := self.GetMetadata(ctx, api.VM_METADATA_KICKSTART_COMPLETED_FLAG, userCred)
if currentFlag == "true" {
return jsonutils.Marshal(map[string]string{
"message": "Kickstart has already been completed for this VM",
"status": "failed",
}), nil
}
if err := self.SetKickstartConfig(ctx, &input, userCred); err != nil {
return nil, errors.Wrap(err, "set kickstart config")
}
// Determine and set kickstart type based on input
kickstartType := determineKickstartType(&input)
if err := self.SetKickstartType(ctx, kickstartType, userCred); err != nil {
return nil, errors.Wrap(err, "set kickstart type")
}
if err := self.SetMetadata(ctx, api.VM_METADATA_KICKSTART_ATTEMPT, "0", userCred); err != nil {
return nil, errors.Wrap(err, "set kickstart attempt")
}
db.OpsLog.LogEvent(self, db.ACT_UPDATE, "set kickstart config", userCred)
// Check if VM needs to be restarted
needRestart := false
if utils.IsInStringArray(self.Status, []string{
api.VM_RUNNING,
api.VM_KICKSTART_INSTALLING,
api.VM_KICKSTART_COMPLETED,
api.VM_KICKSTART_FAILED,
api.VM_BLOCK_STREAM,
api.VM_MIGRATING,
}) {
needRestart = true
}
if err := self.SetKickstartStatus(ctx, api.VM_KICKSTART_PENDING, userCred); err != nil {
return nil, errors.Wrap(err, "set kickstart status")
}
// If VM is running, restart it to apply kickstart config
if needRestart {
driver, err := self.GetDriver()
if err != nil {
return nil, errors.Wrap(err, "get driver for restart")
}
if err := driver.StartGuestRestartTask(self, ctx, userCred, true, nil, "kickstart config updated"); err != nil {
return nil, errors.Wrap(err, "start restart task")
}
return jsonutils.Marshal(map[string]string{
"status": "success",
"message": "kickstart config updated, restarting VM",
}), nil
}
return jsonutils.Marshal(map[string]string{
"status": "success",
}), nil
}
func (self *SGuest) PerformKickstartComplete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input jsonutils.JSONObject) (jsonutils.JSONObject, error) {
// Check if kickstart_completed_flag is already set to true
currentFlag := self.GetMetadata(ctx, api.VM_METADATA_KICKSTART_COMPLETED_FLAG, userCred)
if currentFlag == "true" {
return jsonutils.Marshal(map[string]string{
"status": "success",
"message": "kickstart is already marked as completed",
}), nil
}
// Set the kickstart_completed_flag metadata to true
if err := self.SetMetadata(ctx, api.VM_METADATA_KICKSTART_COMPLETED_FLAG, "true", userCred); err != nil {
return nil, errors.Wrap(err, "set kickstart completed flag")
}
// Update status to VM_KICKSTART_COMPLETED if in kickstart status
if self.IsInKickstartStatus() && self.Status != api.VM_KICKSTART_COMPLETED {
if err := self.SetStatus(ctx, userCred, api.VM_KICKSTART_COMPLETED, "kickstart manually marked as completed"); err != nil {
return nil, errors.Wrap(err, "update kickstart status")
}
}
// Check if restart is requested (default is true)
restart := jsonutils.QueryBoolean(input, "restart", true)
if restart {
// Trigger VM restart to complete the kickstart process
if utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_KICKSTART_INSTALLING, api.VM_KICKSTART_COMPLETED, api.VM_KICKSTART_FAILED}) {
driver, err := self.GetDriver()
if err != nil {
return nil, errors.Wrap(err, "get driver")
}
driver.StartGuestRestartTask(self, ctx, userCred, true, nil, "")
return jsonutils.Marshal(map[string]string{
"status": "success",
"message": "kickstart marked as completed, restarting VM",
}), nil
}
}
db.OpsLog.LogEvent(self, db.ACT_UPDATE, "kickstart manually marked as completed", userCred)
return jsonutils.Marshal(map[string]string{
"status": "success",
"message": "kickstart marked as completed",
}), nil
}
func (self *SGuest) PerformDeleteKickstart(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if self.Status == api.VM_KICKSTART_INSTALLING {
return nil, httperrors.NewInvalidStatusError("cannot delete kickstart config while installation is in progress")
}
if err := self.SetKickstartConfig(ctx, nil, userCred); err != nil {
return nil, errors.Wrap(err, "delete kickstart config")
}
// 如果当前是kickstart状态需要转换回适当的状态
if self.IsInKickstartStatus() {
if self.Status == api.VM_KICKSTART_PENDING {
self.SetStatus(ctx, userCred, api.VM_READY, "kickstart config deleted")
} else if self.Status == api.VM_KICKSTART_COMPLETED {
self.SetStatus(ctx, userCred, api.VM_RUNNING, "kickstart config deleted")
} else if self.Status == api.VM_KICKSTART_FAILED {
self.SetStatus(ctx, userCred, api.VM_READY, "kickstart config deleted")
}
}
// 清理kickstart相关metadata
self.RemoveMetadata(ctx, api.VM_METADATA_KICKSTART_ATTEMPT, userCred)
self.RemoveMetadata(ctx, api.VM_METADATA_KICKSTART_COMPLETED_FLAG, userCred)
db.OpsLog.LogEvent(self, db.ACT_DELETE, "delete kickstart config", userCred)
return jsonutils.Marshal(map[string]string{
"status": "success",
}), nil
}
func (self *SGuest) PerformUpdateKickstartStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerUpdateKickstartStatusInput) (jsonutils.JSONObject, error) {
if err := self.SetKickstartStatus(ctx, input.Status, userCred); err != nil {
return nil, errors.Wrap(err, "update kickstart status")
}
if input.Status == api.VM_KICKSTART_COMPLETED {
if err := self.SetMetadata(ctx, api.VM_METADATA_KICKSTART_COMPLETED_FLAG, "true", userCred); err != nil {
log.Errorf("Failed to set kickstart completed flag for VM %s: %v", self.Name, err)
}
}
if input.ErrorMessage != "" {
db.OpsLog.LogEvent(self, db.ACT_UPDATE, fmt.Sprintf("kickstart status: %s, error: %s", input.Status, input.ErrorMessage), userCred)
} else {
db.OpsLog.LogEvent(self, db.ACT_UPDATE, fmt.Sprintf("kickstart status: %s", input.Status), userCred)
}
return jsonutils.Marshal(map[string]string{
"status": input.Status,
}), nil
}