// 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 (
	"bytes"
	"context"
	"database/sql"
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"

	"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/compare"
	"yunion.io/x/pkg/util/netutils"
	"yunion.io/x/pkg/util/osprofile"
	"yunion.io/x/pkg/util/pinyinutils"
	"yunion.io/x/pkg/util/rbacscope"
	"yunion.io/x/pkg/util/regutils"
	"yunion.io/x/pkg/util/timeutils"
	"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"
	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/compute/options"
	"yunion.io/x/onecloud/pkg/compute/sshkeys"
	devtool_utils "yunion.io/x/onecloud/pkg/devtool/utils"
	"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/util/bitmap"
	"yunion.io/x/onecloud/pkg/util/logclient"
	"yunion.io/x/onecloud/pkg/util/netutils2"
	"yunion.io/x/onecloud/pkg/util/rbacutils"
	"yunion.io/x/onecloud/pkg/util/seclib2"
	"yunion.io/x/onecloud/pkg/util/stringutils2"
)

// +onecloud:swagger-gen-model-singular=server
// +onecloud:swagger-gen-model-plural=servers
type SGuestManager struct {
	db.SVirtualResourceBaseManager
	db.SExternalizedResourceBaseManager
	SDeletePreventableResourceBaseManager

	SHostResourceBaseManager
	SBillingResourceBaseManager
	SNetworkResourceBaseManager
	SDiskResourceBaseManager
	SScalingGroupResourceBaseManager
	db.SMultiArchResourceBaseManager
	db.SRecordChecksumResourceBaseManager
	SHostnameResourceBaseManager

	db.SEncryptedResourceManager
}

var GuestManager *SGuestManager

func init() {
	GuestManager = &SGuestManager{
		SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager(
			SGuest{},
			"guests_tbl",
			"server",
			"servers",
		),
		SRecordChecksumResourceBaseManager: *db.NewRecordChecksumResourceBaseManager(),
	}
	GuestManager.SetVirtualObject(GuestManager)
	GuestManager.SetAlias("guest", "guests")
	GuestManager.NameRequireAscii = false
	notifyclient.AddNotifyDBHookResources(GuestManager.KeywordPlural(), GuestManager.AliasPlural())
}

type SGuest struct {
	db.SVirtualResourceBase

	db.SExternalizedResourceBase

	SBillingResourceBase
	SDeletePreventableResourceBase
	db.SMultiArchResourceBase
	db.SRecordChecksumResourceBase

	SHostnameResourceBase
	SHostResourceBase `width:"36" charset:"ascii" nullable:"true" list:"user" get:"user" index:"true"`

	db.SEncryptedResource

	// CPU插槽(socket)的数量
	CpuSockets int `nullable:"false" default:"1" list:"user" create:"optional"`
	// CPU核(core)的数量， VcpuCount = CpuSockets * (cores per socket)，例如 2颗CPU，每颗CPU8核，则 VcpuCount=2*8=16
	VcpuCount int `nullable:"false" default:"1" list:"user" create:"optional"`
	// 内存大小, 单位MB
	VmemSize int `nullable:"false" list:"user" create:"required"`
	// CPU 内存绑定信息
	CpuNumaPin jsonutils.JSONObject `nullable:"true" get:"user" update:"user" create:"optional"`
	// 额外分配的 CPU 数量
	ExtraCpuCount int `nullable:"false" default:"0" list:"user" create:"optional"`

	// 启动顺序
	BootOrder string `width:"8" charset:"ascii" nullable:"true" default:"cdn" list:"user" update:"user" create:"optional"`

	// 关机操作类型
	// example: stop
	ShutdownBehavior string `width:"16" charset:"ascii" default:"stop" list:"user" update:"user" create:"optional"`

	// 关机收费模式
	// example: keep_charging, stop_charging
	ShutdownMode string `width:"16" charset:"ascii" default:"keep_charging" list:"user"`

	// 秘钥对Id
	KeypairId string `width:"36" charset:"ascii" nullable:"true" list:"user" create:"optional"`

	// 备份机所在宿主机Id
	BackupHostId      string `width:"36" charset:"ascii" nullable:"true" list:"user" get:"user"`
	BackupGuestStatus string `width:"36" charset:"ascii" nullable:"false" default:"init" list:"user" create:"optional" json:"backup_guest_status"`

	// 迁移或克隆的速度
	ProgressMbps float64 `nullable:"false" default:"0" list:"user" create:"optional" update:"user" log:"skip"`

	Vga     string `width:"36" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"`
	Vdi     string `width:"36" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"`
	Machine string `width:"36" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"`
	Bios    string `width:"36" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"`
	// 操作系统类型
	OsType string `width:"36" charset:"ascii" nullable:"true" list:"user" create:"optional"`

	FlavorId string `width:"36" charset:"ascii" nullable:"true" list:"user" create:"optional"`

	// 安全组Id
	// example: default
	SecgrpId string `width:"36" charset:"ascii" nullable:"true" list:"user" get:"user" create:"optional"`
	// 管理员可见安全组Id
	AdminSecgrpId string `width:"36" charset:"ascii" nullable:"true" list:"domain" get:"domain"`

	SrcIpCheck  tristate.TriState `default:"true" create:"optional" list:"user" update:"user"`
	SrcMacCheck tristate.TriState `default:"true" create:"optional" list:"user" update:"user"`

	// 虚拟化技术
	// example: kvm
	Hypervisor string `width:"16" charset:"ascii" nullable:"false" default:"kvm" list:"user" create:"required"`

	// 套餐名称
	InstanceType string `width:"64" charset:"utf8" nullable:"true" list:"user" create:"optional"`

	SshableLastState tristate.TriState `default:"false" list:"user"`

	IsDaemon tristate.TriState `default:"false" list:"admin" create:"admin_optional" update:"admin"`

	// 最大内网带宽
	InternetMaxBandwidthOut int `nullable:"true" list:"user" create:"optional"`
	// 磁盘吞吐量
	Throughput int `nullable:"true" list:"user" create:"optional"`

	QgaStatus string `width:"36" charset:"ascii" nullable:"false" default:"unknown" list:"user" create:"optional"`
	// power_states limit in [on, off, unknown]
	PowerStates string `width:"36" charset:"ascii" nullable:"false" default:"unknown" list:"user" create:"optional"`
	// 健康状态, 仅开机中火运行中有效， 目前只支持阿里云
	HealthStatus string `width:"36" charset:"ascii" nullable:"true" default:"ok" list:"user"`
	// Used for guest rescue
	RescueMode bool `nullable:"false" default:"false" list:"user" create:"optional"`

	// 上次开机时间
	LastStartAt time.Time `json:"last_start_at" list:"user"`

	// 资源池,仅vmware指定调度标签时内部使用
	ResourcePool string `width:"64" charset:"utf8" nullable:"true" create:"optional"`
}

func (manager *SGuestManager) GetPropertyStatistics(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*apis.StatusStatistic, error) {
	ret, err := manager.SVirtualResourceBaseManager.GetPropertyStatistics(ctx, userCred, query)
	if err != nil {
		return nil, err
	}

	q := manager.Query()
	q, err = db.ListItemQueryFilters(manager, ctx, q, userCred, query, policy.PolicyActionList)
	if err != nil {
		return nil, err
	}

	sq := q.SubQuery()
	statQ := sq.Query(sqlchemy.SUM("total_cpu_count", sq.Field("vcpu_count")), sqlchemy.SUM("total_mem_size_mb", sq.Field("vmem_size")))
	err = statQ.First(ret)
	if err != nil {
		return ret, err
	}
	diskQ := DiskManager.Query()
	gdsSQ := GuestdiskManager.Query().SubQuery()
	diskQ = diskQ.Join(gdsSQ, sqlchemy.Equals(diskQ.Field("id"), gdsSQ.Field("disk_id"))).
		Join(sq, sqlchemy.Equals(gdsSQ.Field("guest_id"), sq.Field("id")))
	diskSQ := diskQ.SubQuery()
	return ret, diskSQ.Query(sqlchemy.SUM("total_disk_size_mb", diskSQ.Field("disk_size"))).First(ret)
}

// 云主机实例列表
func (manager *SGuestManager) ListItemFilter(
	ctx context.Context,
	q *sqlchemy.SQuery,
	userCred mcclient.TokenCredential,
	query api.ServerListInput,
) (*sqlchemy.SQuery, error) {
	var err error
	q, err = manager.SHostResourceBaseManager.ListItemFilter(ctx, q, userCred, query.HostFilterListInput)
	if err != nil {
		return nil, errors.Wrap(err, "SHostResourceBaseManager.ListItemFilter")
	}

	q, err = manager.SExternalizedResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ExternalizedResourceBaseListInput)
	if err != nil {
		return nil, errors.Wrap(err, "SExternalizedResourceBaseManager.ListItemFilter")
	}

	q, err = manager.SDeletePreventableResourceBaseManager.ListItemFilter(ctx, q, userCred, query.DeletePreventableResourceBaseListInput)
	if err != nil {
		return nil, errors.Wrap(err, "SDeletePreventableResourceBaseManager.ListItemFilter")
	}

	q, err = manager.SBillingResourceBaseManager.ListItemFilter(ctx, q, userCred, query.BillingResourceListInput)
	if err != nil {
		return nil, errors.Wrap(err, "SBillingResourceBaseManager.ListItemFilter")
	}

	q, err = manager.SVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, query.VirtualResourceListInput)
	if err != nil {
		return nil, errors.Wrap(err, "SVirtualResourceBaseManager.ListItemFilter")
	}

	q, err = manager.SMultiArchResourceBaseManager.ListItemFilter(ctx, q, userCred, query.MultiArchResourceBaseListInput)
	if err != nil {
		return nil, errors.Wrap(err, "MultiArchResourceBaseListInput.ListItemFilter")
	}

	netQ := GuestnetworkManager.Query("guest_id").Snapshot()
	netQ, err = manager.SNetworkResourceBaseManager.ListItemFilter(ctx, netQ, userCred, query.NetworkFilterListInput)
	if err != nil {
		return nil, errors.Wrap(err, "SNetworkResourceBaseManager.ListItemFilter")
	}
	if netQ.IsAltered() {
		q = q.In("id", netQ.SubQuery())
	}

	//diskQ := GuestdiskManager.Query("guest_id").Snapshot()
	//diskQ, err = manager.SDiskResourceBaseManager.ListItemFilter(ctx, diskQ, userCred, query.DiskFilterListInput)
	//if err != nil {
	//	return nil, errors.Wrap(err, "SDiskResourceBaseManager.ListItemFilter")
	//}
	//if diskQ.IsAltered() {
	//	q = q.In("id", diskQ.SubQuery())
	//}

	scalingGroupQ := ScalingGroupGuestManager.Query("guest_id").Snapshot()
	scalingGroupQ, err = manager.SScalingGroupResourceBaseManager.ListItemFilter(ctx, scalingGroupQ, userCred, query.ScalingGroupFilterListInput)
	if err != nil {
		return nil, errors.Wrap(err, "SScaligGroupResourceBaseManager.ListItemFilter")
	}
	if scalingGroupQ.IsAltered() {
		q = q.In("id", scalingGroupQ.SubQuery())
	}

	hypervisorList := query.Hypervisor
	if len(hypervisorList) > 0 {
		q = q.In("hypervisor", hypervisorList)
	}

	resourceTypeStr := query.ResourceType
	if len(resourceTypeStr) > 0 {
		hosts := HostManager.Query().SubQuery()
		subq := hosts.Query(hosts.Field("id"))
		switch resourceTypeStr {
		case api.HostResourceTypeShared:
			subq = subq.Filter(
				sqlchemy.OR(
					sqlchemy.IsNullOrEmpty(hosts.Field("resource_type")),
					sqlchemy.Equals(hosts.Field("resource_type"), resourceTypeStr),
				),
			)
		default:
			subq = subq.Equals("resource_type", resourceTypeStr)
		}

		q = q.In("host_id", subq.SubQuery())
	}

	hostFilter := query.GetAllGuestsOnHost
	if len(hostFilter) > 0 {
		host, _ := HostManager.FetchByIdOrName(ctx, nil, hostFilter)
		if host == nil {
			return nil, httperrors.NewResourceNotFoundError("host %s not found", hostFilter)
		}
		q.Filter(sqlchemy.OR(sqlchemy.Equals(q.Field("host_id"), host.GetId()),
			sqlchemy.Equals(q.Field("backup_host_id"), host.GetId())))
	}

	secgrpFilter := query.SecgroupId
	if len(secgrpFilter) > 0 {
		var notIn = false
		// HACK FOR NOT IN SECGROUP
		if strings.HasPrefix(secgrpFilter, "!") {
			secgrpFilter = secgrpFilter[1:]
			notIn = true
		}
		secgrpIds := []string{}
		secgrps := []SSecurityGroup{}
		sgq := SecurityGroupManager.Query()
		sgq = sgq.Filter(sqlchemy.OR(sqlchemy.Equals(sgq.Field("id"), secgrpFilter), sqlchemy.Equals(sgq.Field("name"), secgrpFilter)))
		if err := db.FetchModelObjects(SecurityGroupManager, sgq, &secgrps); err != nil {
			return nil, err
		}
		if len(secgrps) == 0 {
			return nil, httperrors.NewResourceNotFoundError("secgroup %s not found", secgrpFilter)
		}

		for _, secgrp := range secgrps {
			secgrpIds = append(secgrpIds, secgrp.Id)
		}

		isAdmin := false
		// admin := (query.VirtualResourceListInput.Admin != nil && *query.VirtualResourceListInput.Admin)
		allowScope, _ := policy.PolicyManager.AllowScope(userCred, consts.GetServiceType(), manager.KeywordPlural(), policy.PolicyActionList)
		if allowScope == rbacscope.ScopeSystem || allowScope == rbacscope.ScopeDomain {
			isAdmin = true
		}

		filters := []sqlchemy.ICondition{}
		if notIn {
			filters = append(filters, sqlchemy.NotIn(q.Field("id"),
				GuestsecgroupManager.Query("guest_id").In("secgroup_id", secgrpIds).SubQuery()))
			filters = append(filters, sqlchemy.NotIn(q.Field("secgrp_id"), secgrpIds))
			if isAdmin {
				filters = append(filters, sqlchemy.NotIn(q.Field("admin_secgrp_id"), secgrpIds))
			}
			q = q.Filter(sqlchemy.AND(filters...))
		} else {
			filters = append(filters, sqlchemy.In(q.Field("id"),
				GuestsecgroupManager.Query("guest_id").In("secgroup_id", secgrpIds).SubQuery()))
			filters = append(filters, sqlchemy.In(q.Field("secgrp_id"), secgrpIds))
			if isAdmin {
				filters = append(filters, sqlchemy.In(q.Field("admin_secgrp_id"), secgrpIds))
			}
			q = q.Filter(sqlchemy.OR(filters...))
		}
	}

	var eipMode string
	usableServerForEipFilter := query.UsableServerForEip
	if len(usableServerForEipFilter) > 0 {
		eipObj, err := ElasticipManager.FetchByIdOrName(ctx, userCred, usableServerForEipFilter)
		if err != nil {
			if err == sql.ErrNoRows {
				return nil, httperrors.NewResourceNotFoundError("eip %s not found", usableServerForEipFilter)
			}
			return nil, httperrors.NewGeneralError(err)
		}
		eip := eipObj.(*SElasticip)

		if eip.GetProviderName() == api.CLOUD_PROVIDER_AWS {
			eipMode = api.EIP_MODE_STANDALONE_EIP
		}

		if len(eip.NetworkId) > 0 {
			sq := GuestnetworkManager.Query("guest_id").Equals("network_id", eip.NetworkId).SubQuery()
			q = q.NotIn("id", sq)
			if cp := eip.GetCloudprovider(); cp == nil || cp.Provider == api.CLOUD_PROVIDER_ONECLOUD {
				gnq := GuestnetworkManager.Query().SubQuery()
				nq := NetworkManager.Query().SubQuery()
				wq := WireManager.Query().SubQuery()
				vq := VpcManager.Query().SubQuery()
				q.Join(gnq, sqlchemy.Equals(gnq.Field("guest_id"), q.Field("id")))
				q.Join(nq, sqlchemy.Equals(nq.Field("id"), gnq.Field("network_id")))
				q.Join(wq, sqlchemy.Equals(wq.Field("id"), nq.Field("wire_id")))
				q.Join(vq, sqlchemy.Equals(vq.Field("id"), wq.Field("vpc_id")))
				q.Filter(sqlchemy.IsNullOrEmpty(gnq.Field("eip_id")))
				q.Filter(sqlchemy.NotEquals(vq.Field("id"), api.DEFAULT_VPC_ID))
				// vpc provider thing will be handled ok below
			}
		}

		hostTable := HostManager.Query().SubQuery()
		zoneTable := ZoneManager.Query().SubQuery()
		hostQ := hostTable.Query(hostTable.Field("id"))
		hostQ = hostQ.Join(zoneTable,
			sqlchemy.Equals(zoneTable.Field("id"), hostTable.Field("zone_id")))
		if eip.ManagerId != "" {
			hostQ = hostQ.Equals("manager_id", eip.ManagerId)
		} else {
			hostQ = hostQ.IsNullOrEmpty("manager_id")
		}
		region, err := eip.GetRegion()
		if err != nil {
			return nil, httperrors.NewGeneralError(errors.Wrapf(err, "eip.GetRegion"))
		}
		regionTable := CloudregionManager.Query().SubQuery()
		sq := hostQ.Join(regionTable, sqlchemy.Equals(zoneTable.Field("cloudregion_id"), regionTable.Field("id"))).
			Filter(sqlchemy.Equals(regionTable.Field("id"), region.GetId())).SubQuery()
		q = q.In("host_id", sq)
	}

	if len(query.IpAddrs) > 0 {
		// 如果只有一个ip地址，则使用正则匹配，否则使用等于匹配
		cmpFunc := sqlchemy.Equals
		if len(query.IpAddrs) == 1 {
			cmpFunc = func(f sqlchemy.IQueryField, v interface{}) sqlchemy.ICondition {
				return sqlchemy.Regexp(f, v.(string))
			}
		}

		grpnets := GroupnetworkManager.Query().SubQuery()
		vipq := GroupguestManager.Query("guest_id")
		conditions := []sqlchemy.ICondition{}
		for _, ipAddr := range query.IpAddrs {
			conditions = append(conditions, cmpFunc(grpnets.Field("ip_addr"), ipAddr))
			conditions = append(conditions, cmpFunc(grpnets.Field("ip6_addr"), ipAddr))
		}
		vipq = vipq.Join(grpnets, sqlchemy.Equals(grpnets.Field("group_id"), vipq.Field("group_id"))).Filter(
			sqlchemy.OR(conditions...),
		)

		grpeips := ElasticipManager.Query().Equals("associate_type", api.EIP_ASSOCIATE_TYPE_INSTANCE_GROUP).SubQuery()
		conditions = []sqlchemy.ICondition{}
		for _, ipAddr := range query.IpAddrs {
			conditions = append(conditions, cmpFunc(grpeips.Field("ip_addr"), ipAddr))
		}
		vipeipq := GroupguestManager.Query("guest_id")
		vipeipq = vipeipq.Join(grpeips, sqlchemy.Equals(grpeips.Field("associate_id"), vipeipq.Field("group_id"))).Filter(
			sqlchemy.OR(conditions...),
		)

		gnQ := GuestnetworkManager.Query("guest_id")
		conditions = []sqlchemy.ICondition{}
		for _, ipAddr := range query.IpAddrs {
			conditions = append(conditions, cmpFunc(gnQ.Field("ip_addr"), ipAddr))
			conditions = append(conditions, cmpFunc(gnQ.Field("ip6_addr"), ipAddr))
		}
		gn := gnQ.Filter(sqlchemy.OR(conditions...))

		guestEipQ := ElasticipManager.Query("associate_id").Equals("associate_type", api.EIP_ASSOCIATE_TYPE_SERVER)
		conditions = []sqlchemy.ICondition{}
		for _, ipAddr := range query.IpAddrs {
			conditions = append(conditions, cmpFunc(guestEipQ.Field("ip_addr"), ipAddr))
		}
		guestEip := guestEipQ.Filter(sqlchemy.OR(conditions...))

		metadataQ := db.Metadata.Query("obj_id")
		conditions = []sqlchemy.ICondition{}
		for _, ipAddr := range query.IpAddrs {
			conditions = append(conditions, sqlchemy.AND(
				cmpFunc(metadataQ.Field("value"), ipAddr),
				sqlchemy.Equals(metadataQ.Field("key"), "sync_ips"),
				sqlchemy.Equals(metadataQ.Field("obj_type"), "server"),
			))
		}
		metadataQ = metadataQ.Filter(sqlchemy.OR(conditions...))

		q = q.Filter(sqlchemy.OR(
			sqlchemy.In(q.Field("id"), gn.SubQuery()),
			sqlchemy.In(q.Field("id"), guestEip.SubQuery()),
			sqlchemy.In(q.Field("id"), vipq.SubQuery()),
			sqlchemy.In(q.Field("id"), vipeipq.SubQuery()),
			sqlchemy.In(q.Field("id"), metadataQ.SubQuery()),
		))
	}

	diskFilter := query.AttachableServersForDisk
	if len(diskFilter) > 0 {
		diskI, _ := DiskManager.FetchByIdOrName(ctx, userCred, diskFilter)
		if diskI == nil {
			return nil, httperrors.NewResourceNotFoundError("disk %s not found", diskFilter)
		}
		disk := diskI.(*SDisk)
		guestdisks := GuestdiskManager.Query().SubQuery()
		count, err := guestdisks.Query().Equals("disk_id", disk.Id).CountWithError()
		if err != nil {
			return nil, httperrors.NewInternalServerError("checkout guestdisk count fail %s", err)
		}
		if count > 0 {
			sgq := guestdisks.Query(guestdisks.Field("guest_id")).Equals("disk_id", disk.Id).SubQuery()
			q = q.Filter(sqlchemy.In(q.Field("id"), sgq))
		} else {
			hosts := HostManager.Query().SubQuery()
			hoststorages := HoststorageManager.Query().SubQuery()
			storages := StorageManager.Query().SubQuery()
			sq := hosts.Query(hosts.Field("id")).
				Join(hoststorages, sqlchemy.Equals(hoststorages.Field("host_id"), hosts.Field("id"))).
				Join(storages, sqlchemy.Equals(storages.Field("id"), hoststorages.Field("storage_id"))).
				Filter(sqlchemy.Equals(storages.Field("id"), disk.StorageId)).SubQuery()
			q = q.In("host_id", sq)
		}
	}

	withEip := (query.WithEip != nil && *query.WithEip)
	withoutEip := (query.WithoutEip != nil && *query.WithoutEip) || (query.EipAssociable != nil && *query.EipAssociable)
	if withEip || withoutEip {
		eips := ElasticipManager.Query().SubQuery()
		sq := eips.Query(eips.Field("associate_id")).Equals("associate_type", api.EIP_ASSOCIATE_TYPE_SERVER)
		sq = sq.IsNotNull("associate_id").IsNotEmpty("associate_id")
		if len(eipMode) > 0 {
			sq = sq.Equals("mode", eipMode)
		}

		if withEip {
			q = q.In("id", sq)
		} else if withoutEip {
			q = q.NotIn("id", sq)
		}
	}

	if query.EipAssociable != nil {
		sq1 := NetworkManager.Query("id")
		sq2 := WireManager.Query().SubQuery()
		sq3 := VpcManager.Query().SubQuery()
		sq1 = sq1.Join(sq2, sqlchemy.Equals(sq1.Field("wire_id"), sq2.Field("id")))
		sq1 = sq1.Join(sq3, sqlchemy.Equals(sq2.Field("vpc_id"), sq3.Field("id")))
		cond1 := []string{api.VPC_EXTERNAL_ACCESS_MODE_EIP, api.VPC_EXTERNAL_ACCESS_MODE_EIP_DISTGW}
		if *query.EipAssociable {
			sq1 = sq1.Filter(sqlchemy.In(sq3.Field("external_access_mode"), cond1))
		} else {
			sq1 = sq1.Filter(sqlchemy.NotIn(sq3.Field("external_access_mode"), cond1))
		}
		sq := GuestnetworkManager.Query("guest_id").In("network_id", sq1)
		q = q.In("id", sq)
	}

	devTypeQ := func(q *sqlchemy.SQuery, checkType, backup *bool, dType string, conditions []sqlchemy.ICondition) []sqlchemy.ICondition {
		if checkType != nil {
			isodev := IsolatedDeviceManager.Query().SubQuery()
			isodevCons := []sqlchemy.ICondition{sqlchemy.IsNotNull(isodev.Field("guest_id"))}
			if len(dType) > 0 {
				isodevCons = append(isodevCons, sqlchemy.Startswith(isodev.Field("dev_type"), dType))
			}
			sgq := isodev.Query(isodev.Field("guest_id")).Filter(sqlchemy.AND(isodevCons...))
			cond := sqlchemy.NotIn
			if *checkType {
				cond = sqlchemy.In
			}
			if dType == "GPU" {
				sq := ServerSkuManager.Query("name").GT("gpu_count", 0).Distinct().SubQuery()
				if backup != nil {
					afterAnd := sqlchemy.AND
					if *backup {
						backupCond := sqlchemy.IsNotEmpty(q.Field("backup_host_id"))
						conditions = append(conditions, afterAnd(cond(q.Field("instance_type"), sq), backupCond))
					} else {
						backupCond := sqlchemy.IsEmpty(q.Field("backup_host_id"))
						conditions = append(conditions, afterAnd(cond(q.Field("instance_type"), sq), backupCond))
					}
				} else {
					conditions = append(conditions, cond(q.Field("instance_type"), sq))
				}
			} else {
				if backup != nil {
					afterAnd := sqlchemy.AND
					if *backup {
						backupCond := sqlchemy.IsNotEmpty(q.Field("backup_host_id"))
						conditions = append(conditions, afterAnd(cond(q.Field("id"), sgq), backupCond))
					} else {
						backupCond := sqlchemy.IsEmpty(q.Field("backup_host_id"))
						conditions = append(conditions, afterAnd(cond(q.Field("id"), sgq), backupCond))
					}
				} else {
					conditions = append(conditions, cond(q.Field("id"), sgq))
				}
			}
			return conditions
		}
		return conditions
	}

	conditions := []sqlchemy.ICondition{}
	if len(query.ServerType) > 0 {
		var trueVal, falseVal = true, false
		for _, serverType := range query.ServerType {
			switch serverType {
			case "normal":
				query.Normal = &falseVal
				query.Backup = &falseVal
			case "gpu":
				query.Gpu = &trueVal
				query.Backup = &falseVal
				sq := ServerSkuManager.Query("name").IsNotEmpty("gpu_spec").Distinct()
				conditions = append(conditions, sqlchemy.In(q.Field("instance_type"), sq.SubQuery()))
			case "backup":
				query.Gpu = &falseVal
				query.Backup = &trueVal
			case "usb":
				query.Usb = &trueVal
				query.Backup = &falseVal
			default:
				query.CustomDevType = serverType
				query.Backup = &falseVal
			}

			conditions = devTypeQ(q, query.Normal, query.Backup, "", conditions)
			conditions = devTypeQ(q, query.Gpu, query.Backup, "GPU", conditions)
			conditions = devTypeQ(q, query.Usb, query.Backup, api.USB_TYPE, conditions)
			if len(query.CustomDevType) > 0 {
				ct := true
				conditions = devTypeQ(q, &ct, query.Backup, query.CustomDevType, conditions)
			}
			query.Normal = nil
			query.Gpu = nil
			query.Backup = nil
		}
	}

	if len(conditions) > 0 {
		q = q.Filter(sqlchemy.OR(conditions...))
	}

	groupFilter := query.GroupId
	if len(groupFilter) != 0 {
		groupObj, err := GroupManager.FetchByIdOrName(ctx, userCred, groupFilter)
		if err != nil {
			return nil, httperrors.NewNotFoundError("group %s not found", groupFilter)
		}
		// queryDict.Add(jsonutils.NewString(groupObj.GetId()), "group")
		ggSub := GroupguestManager.Query("guest_id").Equals("group_id", groupObj.GetId()).SubQuery()
		q = q.Join(ggSub, sqlchemy.Equals(ggSub.Field("guest_id"), q.Field("id")))
	}

	/*orderByDisk := query.OrderByDisk
	if orderByDisk == "asc" || orderByDisk == "desc" {
		guestdisks := GuestdiskManager.Query().SubQuery()
		disks := DiskManager.Query().SubQuery()
		guestdiskQ := guestdisks.Query(
			guestdisks.Field("guest_id"),
			sqlchemy.SUM("disks_size", disks.Field("disk_size")),
		)

		guestdiskQ = guestdiskQ.LeftJoin(disks, sqlchemy.Equals(guestdiskQ.Field("disk_id"), disks.Field("id")))
		guestdiskSQ := guestdiskQ.GroupBy(guestdiskQ.Field("guest_id")).SubQuery()

		q = q.LeftJoin(guestdiskSQ, sqlchemy.Equals(q.Field("id"), guestdiskSQ.Field("guest_id")))
		switch orderByDisk {
		case "asc":
			q = q.Asc(guestdiskSQ.Field("disks_size"))
		case "desc":
			q = q.Desc(guestdiskSQ.Field("disks_size"))
		}
	}*/

	if len(query.OsType) > 0 {
		q = q.In("os_type", query.OsType)
	}
	if len(query.OsDist) > 0 {
		metaSQ := db.Metadata.Query().Equals("key", "os_distribution").In("value", query.OsDist).SubQuery()
		q = q.Join(metaSQ, sqlchemy.Equals(q.Field("id"), metaSQ.Field("obj_id")))
	}
	if len(query.VcpuCount) > 0 {
		q = q.In("vcpu_count", query.VcpuCount)
	}
	if len(query.VmemSize) > 0 {
		q = q.In("vmem_size", query.VmemSize)
	}
	if len(query.BootOrder) > 0 {
		q = q.In("boot_order", query.BootOrder)
	}
	if len(query.Vga) > 0 {
		q = q.In("vga", query.Vga)
	}
	if len(query.Vdi) > 0 {
		q = q.In("vdi", query.Vdi)
	}
	if len(query.Machine) > 0 {
		q = q.In("machine", query.Machine)
	}
	if len(query.Bios) > 0 {
		q = q.In("bios", query.Bios)
	}
	if query.SrcIpCheck != nil {
		if *query.SrcIpCheck {
			q = q.IsTrue("src_ip_check")
		} else {
			q = q.IsFalse("src_ip_check")
		}
	}
	if query.SrcMacCheck != nil {
		if *query.SrcMacCheck {
			q = q.IsTrue("src_mac_check")
		} else {
			q = q.IsFalse("src_mac_check")
		}
	}
	if len(query.InstanceType) > 0 {
		q = q.In("instance_type", query.InstanceType)
	}
	if query.WithHost != nil {
		if *query.WithHost {
			q = q.IsNotEmpty("host_id")
		} else {
			q = q.IsNullOrEmpty("host_id")
		}
	}
	if len(query.SnapshotpolicyId) > 0 {
		sp := SnapshotPolicyResourceManager.Query("resource_id").
			Equals("resource_type", api.SNAPSHOT_POLICY_TYPE_SERVER).
			Equals("snapshotpolicy_id", query.SnapshotpolicyId).SubQuery()
		q = q.In("id", sp)
	}

	if query.BindingSnapshotpolicy != nil {
		spjsq := SnapshotPolicyResourceManager.Query("resource_id").
			Equals("resource_type", api.SNAPSHOT_POLICY_TYPE_SERVER).
			SubQuery()
		if *query.BindingSnapshotpolicy {
			q = q.In("id", spjsq)
		} else {
			q = q.NotIn("id", spjsq)
		}
	}
	if query.BindingDisksSnapshotpolicy != nil {
		guestDisks := GuestdiskManager.Query("guest_id")
		sq := SnapshotPolicyResourceManager.Query("resource_id").Equals("resource_type", api.SNAPSHOT_POLICY_TYPE_DISK).SubQuery()
		gdsq := guestDisks.Join(sq, sqlchemy.Equals(guestDisks.Field("disk_id"), sq.Field("resource_id"))).SubQuery()
		if *query.BindingDisksSnapshotpolicy {
			q = q.In("id", gdsq)
		} else {
			q = q.NotIn("id", gdsq)
		}
	}

	return q, nil
}

func (manager *SGuestManager) ExtraSearchConditions(ctx context.Context, q *sqlchemy.SQuery, like string) []sqlchemy.ICondition {
	var sq *sqlchemy.SSubQuery
	if len(like) > 1 {
		switch {
		case strings.Contains(like, "."):
			sq = GuestnetworkManager.Query("guest_id").Contains("ip_addr", like).SubQuery()
		case strings.Contains(like, ":"):
			sq = GuestnetworkManager.Query("guest_id").Contains("mac_addr", like).SubQuery()
		}
	}
	if sq != nil {
		return []sqlchemy.ICondition{sqlchemy.In(q.Field("id"), sq)}
	}
	return nil
}

func (manager *SGuestManager) OrderByExtraFields(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query api.ServerListInput) (*sqlchemy.SQuery, error) {
	var err error

	q, err = manager.SVirtualResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.VirtualResourceListInput)
	if err != nil {
		return nil, errors.Wrap(err, "SVirtualResourceBaseManager.OrderByExtraFields")
	}
	q, err = manager.SHostResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.HostFilterListInput)
	if err != nil {
		return nil, errors.Wrap(err, "SHostResourceBaseManager.OrderByExtraFields")
	}
	fields := manager.SNetworkResourceBaseManager.GetOrderByFields(query.NetworkFilterListInput)
	if db.NeedOrderQuery(fields) {
		netQ := GuestnetworkManager.Query("guest_id", "network_id").SubQuery()
		q = q.LeftJoin(netQ, sqlchemy.Equals(q.Field("id"), netQ.Field("guest_id"))).Distinct()
		q, err = manager.SNetworkResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.NetworkFilterListInput)
		if err != nil {
			return nil, errors.Wrap(err, "SNetworkResourceBaseManager.OrderByExtraFields")
		}
	}

	if db.NeedOrderQuery([]string{query.OrderByOsDist}) {
		meta := db.Metadata.Query().Equals("key", "os_distribution").SubQuery()
		q = q.LeftJoin(meta, sqlchemy.Equals(q.Field("id"), meta.Field("obj_id")))
		db.OrderByFields(q, []string{query.OrderByOsDist}, []sqlchemy.IQueryField{meta.Field("value")})
	}

	if db.NeedOrderQuery([]string{query.OrderByDisk}) {
		guestdisks := GuestdiskManager.Query().SubQuery()
		disks := DiskManager.Query().SubQuery()
		guestdiskQ := guestdisks.Query(
			guestdisks.Field("guest_id"),
			sqlchemy.SUM("disks_size", disks.Field("disk_size")),
		)
		guestdiskQ = guestdiskQ.LeftJoin(disks, sqlchemy.Equals(guestdiskQ.Field("disk_id"), disks.Field("id")))
		guestdiskSQ := guestdiskQ.GroupBy(guestdiskQ.Field("guest_id")).SubQuery()

		q = q.LeftJoin(guestdiskSQ, sqlchemy.Equals(q.Field("id"), guestdiskSQ.Field("guest_id")))
		db.OrderByFields(q, []string{query.OrderByDisk}, []sqlchemy.IQueryField{guestdiskSQ.Field("disks_size")})
	}
	if db.NeedOrderQuery([]string{query.OrderByIp}) {
		guestnet := GuestnetworkManager.Query("guest_id", "ip_addr").SubQuery()
		q.AppendField(q.QueryFields()...)
		q.AppendField(guestnet.Field("ip_addr"))
		q = q.LeftJoin(guestnet, sqlchemy.Equals(q.Field("id"), guestnet.Field("guest_id")))
		db.OrderByFields(q, []string{query.OrderByIp}, []sqlchemy.IQueryField{sqlchemy.INET_ATON(q.Field("ip_addr"))})
	}
	return q, nil
}

func (manager *SGuestManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
	var err error
	q, err = manager.SVirtualResourceBaseManager.QueryDistinctExtraField(q, field)
	if err == nil {
		return q, nil
	}
	q, err = manager.SHostResourceBaseManager.QueryDistinctExtraField(q, field)
	if err == nil {
		return q, nil
	}
	if field == "os_dist" {
		metaQuery := db.Metadata.Query("obj_id", "value").Equals("key", "os_distribution").SubQuery()
		q = q.AppendField(metaQuery.Field("value", field)).Distinct()
		q = q.Join(metaQuery, sqlchemy.Equals(q.Field("id"), metaQuery.Field("obj_id")))
		q.GroupBy(metaQuery.Field("value"))
		return q, nil
	}
	guestnets := GuestnetworkManager.Query("guest_id", "network_id").SubQuery()
	q = q.LeftJoin(guestnets, sqlchemy.Equals(q.Field("id"), guestnets.Field("guest_id")))
	q, err = manager.SNetworkResourceBaseManager.QueryDistinctExtraField(q, field)
	if err == nil {
		return q, nil
	}
	guestdisks := GuestdiskManager.Query("guest_id", "disk_id").SubQuery()
	q = q.LeftJoin(guestdisks, sqlchemy.Equals(q.Field("id"), guestdisks.Field("guest_id")))
	q, err = manager.SDiskResourceBaseManager.QueryDistinctExtraField(q, field)
	if err == nil {
		return q, nil
	}
	return q, httperrors.ErrNotFound
}

func (manager *SGuestManager) QueryDistinctExtraFields(q *sqlchemy.SQuery, resource string, fields []string) (*sqlchemy.SQuery, error) {
	switch resource {
	case NetworkManager.Keyword():
		guestnets := GuestnetworkManager.Query("guest_id", "network_id").SubQuery()
		q = q.LeftJoin(guestnets, sqlchemy.Equals(q.Field("id"), guestnets.Field("guest_id")))

		return manager.SNetworkResourceBaseManager.QueryDistinctExtraFields(q, resource, fields)
	}
	return q, httperrors.ErrNotFound
}

func (manager *SGuestManager) initHostname() error {
	guests := []SGuest{}
	q := manager.Query().IsNullOrEmpty("hostname")
	err := db.FetchModelObjects(manager, q, &guests)
	if err != nil {
		return errors.Wrapf(err, "db.FetchModelObjects")
	}
	for i := range guests {
		db.Update(&guests[i], func() error {
			hostname, _ := manager.SHostnameResourceBaseManager.ValidateHostname(
				guests[i].Hostname,
				guests[i].OsType,
				api.HostnameInput{
					Hostname: guests[i].Name,
				},
			)
			guests[i].Hostname = hostname.Hostname
			return nil
		})
	}
	return nil
}

func (manager *SGuestManager) clearSecgroups() error {
	guests := make([]SGuest, 0, 10)
	q := manager.Query()
	q = q.In("hypervisor", []string{api.HYPERVISOR_ESXI, api.HYPERVISOR_NUTANIX}).Filter(
		sqlchemy.OR(
			sqlchemy.IsNotEmpty(q.Field("secgrp_id")),
			sqlchemy.IsNotEmpty(q.Field("admin_secgrp_id")),
		),
	)
	err := db.FetchModelObjects(manager, q, &guests)
	if err != nil {
		return errors.Wrap(err, "db.FetchModelObjects")
	}
	// remove secgroup for esxi nutanix guest
	for i := range guests {
		db.Update(&guests[i], func() error {
			guests[i].SecgrpId = ""
			guests[i].AdminSecgrpId = ""
			return nil
		})
	}
	return nil
}

func (manager *SGuestManager) initAdminSecgroupId() error {
	{
		err := manager.initAdminSecgroupIdForHypervisor(api.HYPERVISOR_KVM)
		if err != nil {
			return errors.Wrap(err, "initAdminSecgroupIdForKvm")
		}
	}
	{
		err := manager.initAdminSecgroupIdForHypervisor(api.HYPERVISOR_POD)
		if err != nil {
			return errors.Wrap(err, "initAdminSecgroupIdForContainer")
		}
	}
	return nil
}

func (manager *SGuestManager) initAdminSecgroupIdForHypervisor(hypervisor string) error {
	secGrpId := options.Options.GetDefaultAdminSecurityGroupId(hypervisor)
	if len(secGrpId) == 0 {
		return nil
	}
	adminSec, _ := SecurityGroupManager.FetchSecgroupById(secGrpId)
	if adminSec == nil {
		return nil
	}
	adminSecId := adminSec.Id
	guests := make([]SGuest, 0, 10)
	q := manager.Query()
	q = q.Equals("hypervisor", hypervisor).IsNullOrEmpty("admin_secgrp_id")
	err := db.FetchModelObjects(manager, q, &guests)
	if err != nil {
		return errors.Wrap(err, "db.FetchModelObjects")
	}
	// remove secgroup for esxi nutanix guest
	for i := range guests {
		db.Update(&guests[i], func() error {
			guests[i].AdminSecgrpId = adminSecId
			return nil
		})
	}
	return nil
}

func (manager *SGuestManager) InitializeData() error {
	if err := manager.initHostname(); err != nil {
		return errors.Wrap(err, "initHostname")
	}
	if err := manager.clearSecgroups(); err != nil {
		return errors.Wrap(err, "cleanSecgroups")
	}
	if err := manager.initAdminSecgroupId(); err != nil {
		return errors.Wrap(err, "initAdminSecgroupId")
	}
	return nil
}

func (guest *SGuest) GetHypervisor() string {
	if len(guest.Hypervisor) == 0 {
		return api.HYPERVISOR_DEFAULT
	}
	return guest.Hypervisor
}

func (guest *SGuest) GetHostType() string {
	host, err := guest.GetHost()
	if err != nil {
		return ""
	}
	return host.HostType
}

func (guest *SGuest) GetRegion() (*SCloudregion, error) {
	hosts := HostManager.Query("zone_id").Equals("id", guest.HostId).SubQuery()
	zones := ZoneManager.Query("cloudregion_id").In("id", hosts).SubQuery()
	q := CloudregionManager.Query().In("id", zones)
	ret := &SCloudregion{}
	ret.SetModelManager(CloudregionManager, ret)
	err := q.First(ret)
	if err != nil {
		return nil, errors.Wrapf(err, "q.First")
	}
	return ret, nil
}

func (guest *SGuest) GetZone() (*SZone, error) {
	hosts := HostManager.Query("zone_id").Equals("id", guest.HostId).SubQuery()
	q := ZoneManager.Query().In("id", hosts)
	ret := &SZone{}
	ret.SetModelManager(ZoneManager, ret)
	err := q.First(ret)
	if err != nil {
		return nil, errors.Wrapf(err, "q.First")
	}
	return ret, nil
}

func (guest *SGuest) GetDriver() (IGuestDriver, error) {
	hypervisor := guest.GetHypervisor()
	region, err := guest.GetRegion()
	if err != nil {
		return nil, errors.Wrapf(err, "GetRegion")
	}
	return GetDriver(hypervisor, region.Provider)
}

func (guest *SGuest) validateDeleteCondition(ctx context.Context, isPurge bool) error {
	if guest.DisableDelete.IsTrue() {
		return httperrors.NewInvalidStatusError("Virtual server is locked, cannot delete")
	}
	if !isPurge && guest.IsNotDeletablePrePaid() {
		return httperrors.NewForbiddenError("not allow to delete prepaid server in valid status")
	}
	return guest.SVirtualResourceBase.ValidateDeleteCondition(ctx, nil)
}

func (guest *SGuest) ValidateDeleteCondition(ctx context.Context, info *api.ServerDetails) error {
	if gotypes.IsNil(info) {
		info = &api.ServerDetails{}
		host, err := guest.GetHost()
		if err != nil {
			return err
		}
		info.HostType = host.HostType
		info.HostEnabled = host.Enabled.Bool()
		info.HostStatus = host.Status
		info.HostServiceStatus = host.HostStatus
	}
	if len(info.HostType) > 0 && guest.GetHypervisor() != api.HYPERVISOR_BAREMETAL {
		if !info.HostEnabled {
			return httperrors.NewInputParameterError("Cannot delete server on disabled host")
		}
		if info.HostServiceStatus != api.HOST_ONLINE {
			return httperrors.NewInputParameterError("Cannot delete server on offline host")
		}
	}
	return guest.validateDeleteCondition(ctx, false)
}

func (guest *SGuest) GetDisksQuery() *sqlchemy.SQuery {
	return GuestdiskManager.Query().Equals("guest_id", guest.Id)
}

func (guest *SGuest) DiskCount() (int, error) {
	return guest.GetDisksQuery().CountWithError()
}

func (guest *SGuest) GetSystemDisk() (*SDisk, error) {
	q := DiskManager.Query().Equals("disk_type", api.DISK_TYPE_SYS)
	gs := GuestdiskManager.Query().SubQuery()
	q = q.Join(gs, sqlchemy.Equals(gs.Field("disk_id"), q.Field("id"))).
		Filter(sqlchemy.Equals(gs.Field("guest_id"), guest.Id))

	count, err := q.CountWithError()
	if err != nil {
		return nil, err
	}
	if count > 1 {
		return nil, sqlchemy.ErrDuplicateEntry
	}
	if count == 0 {
		return nil, sql.ErrNoRows
	}
	disk := &SDisk{}
	err = q.First(disk)
	if err != nil {
		return nil, errors.Wrap(err, "q.First(disk)")
	}
	disk.SetModelManager(DiskManager, disk)
	return disk, nil
}

func (self *SGuest) GetDisks() ([]SDisk, error) {
	gds := GuestdiskManager.Query().SubQuery()
	sq := DiskManager.Query()
	q := sq.Join(gds, sqlchemy.Equals(gds.Field("disk_id"), sq.Field("id"))).Filter(
		sqlchemy.Equals(gds.Field("guest_id"), self.Id),
	).Asc(gds.Field("index"))
	disks := []SDisk{}
	err := db.FetchModelObjects(DiskManager, q, &disks)
	if err != nil {
		return nil, errors.Wrapf(err, "db.FetchModelObjects")
	}
	return disks, nil
}

func (guest *SGuest) GetGuestDisks() ([]SGuestdisk, error) {
	disks := make([]SGuestdisk, 0)
	q := guest.GetDisksQuery().Asc("index")
	err := db.FetchModelObjects(GuestdiskManager, q, &disks)
	if err != nil {
		return nil, errors.Wrapf(err, "db.FetchModelObjects")
	}
	return disks, nil
}

func (guest *SGuest) GetGuestDisk(diskId string) *SGuestdisk {
	guestdisk, err := db.NewModelObject(GuestdiskManager)
	if err != nil {
		log.Errorf("new guestdisk model failed: %s", err)
		return nil
	}
	q := guest.GetDisksQuery()
	err = q.Equals("disk_id", diskId).First(guestdisk)
	if err != nil {
		log.Errorf("GetGuestDisk error: %s", err)
		return nil
	}
	return guestdisk.(*SGuestdisk)
}

func (guest *SGuest) GetNetworksQuery(netId string) *sqlchemy.SQuery {
	q := GuestnetworkManager.Query().Equals("guest_id", guest.Id)
	if len(netId) > 0 {
		q = q.Equals("network_id", netId)
	}
	return q
}

func (guest *SGuest) NetworkCount() (int, error) {
	return guest.GetNetworksQuery("").CountWithError()
}

func (guest *SGuest) GetVpc() (*SVpc, error) {
	q := guest.GetNetworksQuery("")
	guestnic := &SGuestnetwork{}
	err := q.First(guestnic)
	if err != nil {
		return nil, errors.Wrapf(err, "failed getting guest network of %s(%s)", guest.Name, guest.Id)
	}
	guestnic.SetModelManager(GuestnetworkManager, guestnic)
	network, err := guestnic.GetNetwork()
	if err != nil {
		return nil, errors.Wrapf(err, "GetVpc")
	}
	vpc, err := network.GetVpc()
	if err != nil {
		return nil, errors.Wrapf(err, "GetVpc")
	}
	return vpc, nil
}

func (guest *SGuest) IsOneCloudVpcNetwork() (bool, error) {
	gns, err := guest.GetNetworks("")
	if err != nil {
		return false, errors.Wrap(err, "GetNetworks")
	}
	for _, gn := range gns {
		n, _ := gn.GetNetwork()
		if n != nil && n.isOneCloudVpcNetwork() {
			return true, nil
		}
	}
	return false, nil
}

func (guest *SGuest) GetNetworks(netId string) ([]SGuestnetwork, error) {
	guestnics := make([]SGuestnetwork, 0)
	q := guest.GetNetworksQuery(netId).Asc("index")
	err := db.FetchModelObjects(GuestnetworkManager, q, &guestnics)
	if err != nil {
		return nil, errors.Wrapf(err, "db.FetchModelObjects")
	}
	return guestnics, nil
}

func (guest *SGuest) GetSlaveNetworks() ([]SGuestnetwork, error) {
	guestnics := make([]SGuestnetwork, 0)
	q := guest.GetNetworksQuery("").IsNotEmpty("team_with")
	err := db.FetchModelObjects(GuestnetworkManager, q, &guestnics)
	if err != nil {
		return nil, errors.Wrapf(err, "db.FetchModelObjects")
	}
	return guestnics, nil
}

func (guest *SGuest) ConvertEsxiNetworks(targetGuest *SGuest) error {
	gns, err := guest.GetNetworks("")
	if err != nil {
		return err
	}
	var i int
	for ; i < len(gns); i++ {
		_, err = db.Update(&gns[i], func() error {
			gns[i].GuestId = targetGuest.Id
			if gns[i].Driver != "e1000" && gns[i].Driver != "vmxnet3" {
				gns[i].Driver = "e1000"
			}
			return nil
		})
		if err != nil {
			log.Errorf("update guestnetworks failed %s", err)
			break
		}
	}
	if err != nil {
		for j := 0; j < i; j++ {
			_, err = db.Update(&gns[j], func() error {
				gns[j].GuestId = guest.Id
				return nil
			})
			if err != nil {
				log.Errorf("update guestnetworks failed %s", err)
				break
			}
		}
	}
	return err
}

func (guest *SGuest) getGuestnetworkByIndex(networkIndex int) (*SGuestnetwork, error) {
	q := guest.GetNetworksQuery("").Equals("index", networkIndex)

	guestnic := SGuestnetwork{}
	err := q.First(&guestnic)
	if err != nil {
		return nil, err
	}
	guestnic.SetModelManager(GuestnetworkManager, &guestnic)
	return &guestnic, nil
}

func (guest *SGuest) getGuestnetworkByIpOrMac(ipAddr string, ip6Addr string, macAddr string) (*SGuestnetwork, error) {
	q := guest.GetNetworksQuery("")
	if len(ipAddr) > 0 {
		q = q.Equals("ip_addr", ipAddr)
	}
	if len(ip6Addr) > 0 {
		addr, err := netutils.NewIPV6Addr(ip6Addr)
		if err == nil {
			q = q.Equals("ip6_addr", addr.String())
		}
	}
	if len(macAddr) > 0 {
		macAddr = netutils2.FormatMac(macAddr)
		q = q.Equals("mac_addr", macAddr)
	}

	guestnic := SGuestnetwork{}
	err := q.First(&guestnic)
	if err != nil {
		return nil, err
	}
	guestnic.SetModelManager(GuestnetworkManager, &guestnic)
	return &guestnic, nil
}

func (guest *SGuest) GetGuestnetworkByIp(ipAddr string) (*SGuestnetwork, error) {
	return guest.getGuestnetworkByIpOrMac(ipAddr, "", "")
}

func (guest *SGuest) GetGuestnetworkByIp6(ip6Addr string) (*SGuestnetwork, error) {
	return guest.getGuestnetworkByIpOrMac("", ip6Addr, "")
}

func (guest *SGuest) GetGuestnetworkByMac(macAddr string) (*SGuestnetwork, error) {
	return guest.getGuestnetworkByIpOrMac("", "", macAddr)
}

func (guest *SGuest) IsNetworkAllocated() bool {
	guestnics, err := guest.GetNetworks("")
	if err != nil {
		return false
	}
	for _, gn := range guestnics {
		if !gn.IsAllocated() {
			return false
		}
	}
	return true
}

func (guest *SGuest) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
	optAdminSecGrpId := options.Options.GetDefaultAdminSecurityGroupId(guest.Hypervisor)
	if len(guest.SecgrpId) > 0 && len(optAdminSecGrpId) > 0 {
		adminSec, _ := SecurityGroupManager.FetchSecgroupById(optAdminSecGrpId)
		if adminSec != nil {
			guest.AdminSecgrpId = adminSec.Id
		}
	}
	guest.HostId = ""
	err := guest.SEncryptedResource.CustomizeCreate(ctx, userCred, ownerId, data, "server-"+pinyinutils.Text2Pinyin(guest.Name))
	if err != nil {
		return errors.Wrap(err, "EncryptResourceBase.CustomizeCreate")
	}
	return guest.SVirtualResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data)
}

func (guest *SGuest) GetCloudproviderId() string {
	host, _ := guest.GetHost()
	if host != nil {
		return host.GetCloudproviderId()
	}
	return ""
}

func (guest *SGuest) GetHost() (*SHost, error) {
	if len(guest.HostId) > 0 && regutils.MatchUUID(guest.HostId) {
		host, err := HostManager.FetchById(guest.HostId)
		if err != nil {
			return nil, err
		}
		return host.(*SHost), nil
	}
	return nil, fmt.Errorf("empty host id")
}

func (guest *SGuest) SetHostId(userCred mcclient.TokenCredential, hostId string) error {
	if guest.HostId != hostId {
		diff, err := db.Update(guest, func() error {
			guest.HostId = hostId
			return nil
		})
		if err != nil {
			return err
		}
		db.OpsLog.LogEvent(guest, db.ACT_UPDATE, diff, userCred)
	}
	return nil
}

func (guest *SGuest) SetHostIdWithBackup(userCred mcclient.TokenCredential, master, slave string) error {
	diff, err := db.Update(guest, func() error {
		guest.HostId = master
		guest.BackupHostId = slave
		return nil
	})
	if err != nil {
		return err
	}
	db.OpsLog.LogEvent(guest, db.ACT_UPDATE, diff, userCred)
	return err
}

func (guest *SGuest) UpdateCpuNumaPin(
	ctx context.Context, userCred mcclient.TokenCredential,
	schedCpuNumaPin []schedapi.SCpuNumaPin, cpuNumaPinTarget []api.SCpuNumaPin,
) error {
	srcSchedCpuNumaPin := make([]schedapi.SCpuNumaPin, 0)
	err := guest.CpuNumaPin.Unmarshal(&srcSchedCpuNumaPin)
	if err != nil {
		return err
	}
	srcSchedCpuNumaPin = append(srcSchedCpuNumaPin, schedCpuNumaPin...)

	srcCpuNumaPin := make([]api.SCpuNumaPin, 0)
	cpuNumaPinStr := guest.GetMetadata(ctx, api.VM_METADATA_CPU_NUMA_PIN, nil)
	cpuNumaPinJson, err := jsonutils.ParseString(cpuNumaPinStr)
	if err != nil {
		return err
	}
	err = cpuNumaPinJson.Unmarshal(&srcCpuNumaPin)
	if err != nil {
		return err
	}
	srcCpuNumaPin = append(srcCpuNumaPin, cpuNumaPinTarget...)

	diff, err := db.Update(guest, func() error {
		guest.CpuNumaPin = jsonutils.Marshal(srcSchedCpuNumaPin)
		return nil
	})
	if err != nil {
		return err
	}

	var jcpuNumaPin = jsonutils.Marshal(srcCpuNumaPin)
	err = guest.SetMetadata(ctx, api.VM_METADATA_CPU_NUMA_PIN, jcpuNumaPin, userCred)
	if err != nil {
		return err
	}

	db.OpsLog.LogEvent(guest, db.ACT_UPDATE, diff, userCred)
	return nil
}

func (guest *SGuest) SetCpuNumaPin(
	ctx context.Context, userCred mcclient.TokenCredential,
	schedCpuNumaPin []schedapi.SCpuNumaPin, cpuNumaPin []api.SCpuNumaPin,
) error {
	if cpuNumaPin == nil && schedCpuNumaPin != nil {
		cpuNumaPin = make([]api.SCpuNumaPin, len(schedCpuNumaPin))

		vcpuId := 0
		for i := range schedCpuNumaPin {
			cpuNumaPin[i] = api.SCpuNumaPin{
				SizeMB:        schedCpuNumaPin[i].MemSizeMB,
				NodeId:        schedCpuNumaPin[i].NodeId,
				ExtraCpuCount: schedCpuNumaPin[i].ExtraCpuCount,
			}

			if len(schedCpuNumaPin[i].CpuPin) > 0 {
				cpuNumaPin[i].VcpuPin = make([]api.SVCpuPin, len(schedCpuNumaPin[i].CpuPin))
				for j := range schedCpuNumaPin[i].CpuPin {
					cpuNumaPin[i].VcpuPin[j].Pcpu = schedCpuNumaPin[i].CpuPin[j]
					cpuNumaPin[i].VcpuPin[j].Vcpu = vcpuId
					vcpuId += 1
				}
			}
		}
	}

	var cpuNumaPinType string
	var schedCpuNumaPinJ jsonutils.JSONObject
	if schedCpuNumaPin != nil {
		schedCpuNumaPinJ = jsonutils.Marshal(schedCpuNumaPin)
		cpuNumaPinType = api.VM_CPU_NUMA_PIN_SCHEDULER
	} else if cpuNumaPin != nil {
		schedCpuNumaPinJ = jsonutils.Marshal(cpuNumaPin)
	}
	diff, err := db.Update(guest, func() error {
		guest.CpuNumaPin = schedCpuNumaPinJ
		return nil
	})
	if err != nil {
		return err
	}

	var jcpuNumaPin interface{} = ""
	if cpuNumaPin != nil {
		jcpuNumaPin = jsonutils.Marshal(cpuNumaPin)
	}
	metadataMap := map[string]interface{}{
		api.VM_METADATA_CPU_NUMA_PIN:      jcpuNumaPin,
		api.VM_METADATA_CPU_NUMA_PIN_TYPE: cpuNumaPinType,
	}
	err = guest.SetAllMetadata(ctx, metadataMap, userCred)
	if err != nil {
		return err
	}

	db.OpsLog.LogEvent(guest, db.ACT_UPDATE, diff, userCred)
	return err
}

func (guest *SGuest) ValidateResizeDisk(disk *SDisk, storage *SStorage) error {
	drv, err := guest.GetDriver()
	if err != nil {
		return err
	}
	return drv.ValidateResizeDisk(guest, disk, storage)
}

func ValidateMemData(vmemSize int, driver IGuestDriver) (int, error) {
	if vmemSize > 0 {
		maxVmemGb := driver.GetMaxVMemSizeGB()
		if vmemSize < 8 || vmemSize > maxVmemGb*1024 {
			return 0, httperrors.NewInputParameterError("Memory size must be 8MB ~ %d GB", maxVmemGb)
		}
	}
	return vmemSize, nil
}

func ValidateCpuData(vcpuCount int, driver IGuestDriver) (int, error) {
	maxVcpuCount := driver.GetMaxVCpuCount()
	if vcpuCount < 1 || vcpuCount > maxVcpuCount {
		return 0, httperrors.NewInputParameterError("CPU core count must be 1 ~ %d", maxVcpuCount)
	}
	return vcpuCount, nil
}

func ValidateMemCpuData(vmemSize, vcpuCount int, hypervisor, provider string) (int, int, error) {
	if len(hypervisor) == 0 {
		hypervisor = api.HYPERVISOR_DEFAULT
	}
	driver, err := GetDriver(hypervisor, provider)
	if err != nil {
		return 0, 0, err
	}

	vmemSize, err = ValidateMemData(vmemSize, driver)
	if err != nil {
		return 0, 0, err
	}

	vcpuCount, err = ValidateCpuData(vcpuCount, driver)
	if err != nil {
		return 0, 0, err
	}
	return vmemSize, vcpuCount, nil
}

func (self *SGuest) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerUpdateInput) (api.ServerUpdateInput, error) {
	if len(input.Name) > 0 && len(input.Name) < 2 {
		return input, httperrors.NewInputParameterError("name is too short")
	}

	// validate Hostname
	if len(input.Hostname) > 0 {
		if !regutils.MatchDomainName(input.Hostname) {
			return input, httperrors.NewInputParameterError("hostname should be a legal domain name")
		}
	}

	// 避免调度失败的机器修改删除保护时报no rows in result set 错误，导致前端删不掉机器
	if len(self.HostId) > 0 {
		drv, err := self.GetDriver()
		if err != nil {
			return input, err
		}
		input, err = drv.ValidateUpdateData(ctx, self, userCred, input)
		if err != nil {
			return input, errors.Wrap(err, "GetDriver().ValidateUpdateData")
		}
	}

	var err error
	input.VirtualResourceBaseUpdateInput, err = self.SVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, input.VirtualResourceBaseUpdateInput)
	if err != nil {
		return input, errors.Wrap(err, "SVirtualResourceBase.ValidateUpdateData")
	}
	return input, nil
}

func serverCreateInput2ComputeQuotaKeys(input api.ServerCreateInput, ownerId mcclient.IIdentityProvider) (SComputeResourceKeys, error) {
	var keys SComputeResourceKeys
	if len(input.PreferHost) > 0 {
		hostObj, err := HostManager.FetchById(input.PreferHost)
		if err != nil {
			return keys, err
		}
		host := hostObj.(*SHost)
		input.PreferZone = host.ZoneId
		keys.ZoneId = host.ZoneId
	}
	if len(input.PreferWire) > 0 {
		wireObj, err := WireManager.FetchById(input.PreferWire)
		if err != nil {
			return keys, err
		}
		wire := wireObj.(*SWire)
		if len(wire.ZoneId) > 0 {
			input.PreferZone = wire.ZoneId
			keys.ZoneId = wire.ZoneId
		}
	}
	if len(input.PreferZone) > 0 {
		zoneObj, err := ZoneManager.FetchById(input.PreferZone)
		if err != nil {
			return keys, err
		}
		zone := zoneObj.(*SZone)
		input.PreferRegion = zone.CloudregionId
		keys.ZoneId = zone.Id
		keys.RegionId = zone.CloudregionId
	}
	if len(input.PreferRegion) > 0 {
		regionObj, err := CloudregionManager.FetchById(input.PreferRegion)
		if err != nil {
			return keys, err
		}
		region := regionObj.(*SCloudregion)
		keys.RegionId = region.GetId()
		keys.Brand = region.Provider
	}
	return keys, nil
}

func (manager *SGuestManager) BatchPreValidate(
	ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider,
	query jsonutils.JSONObject, data *jsonutils.JSONDict, count int,
) error {
	input, err := manager.validateCreateData(ctx, userCred, ownerId, query, data)
	if err != nil {
		return errors.Wrap(err, "manager.validateCreateData")
	}
	if input.IsSystem == nil || !(*input.IsSystem) {
		err := manager.checkCreateQuota(ctx, userCred, ownerId, *input, input.Backup, count)
		if err != nil {
			return errors.Wrap(err, "manager.checkCreateQuota")
		}
	}
	return nil
}

func parseInstanceSnapshot(ctx context.Context, input *api.ServerCreateInput) (*api.ServerCreateInput, error) {
	ispi, err := InstanceSnapshotManager.FetchByIdOrName(ctx, nil, input.InstanceSnapshotId)
	if err == sql.ErrNoRows {
		return nil, httperrors.NewBadRequestError("can't find instance snapshot %s", input.InstanceSnapshotId)
	}
	if err != nil {
		return nil, httperrors.NewInternalServerError("fetch instance snapshot error %s", err)
	}
	isp := ispi.(*SInstanceSnapshot)
	if isp.Status != api.INSTANCE_SNAPSHOT_READY {
		return nil, httperrors.NewBadRequestError("Instance snapshot not ready")
	}
	input, err = isp.ToInstanceCreateInput(input)
	if len(input.Disks) == 0 {
		return nil, httperrors.NewInputParameterError("there are no disks in this instance snapshot, try another one")
	}
	return input, nil
}

func parseInstanceBackup(ctx context.Context, input *api.ServerCreateInput) (*api.ServerCreateInput, error) {
	ispi, err := InstanceBackupManager.FetchByIdOrName(ctx, nil, input.InstanceBackupId)
	if err == sql.ErrNoRows {
		return nil, httperrors.NewBadRequestError("can't find instance backup %s", input.InstanceBackupId)
	}
	if err != nil {
		return nil, httperrors.NewInternalServerError("fetch instance backup error %s", err)
	}
	isp := ispi.(*SInstanceBackup)
	if isp.Status != api.INSTANCE_BACKUP_STATUS_READY && isp.Status != api.INSTANCE_BACKUP_STATUS_RECOVERY {
		return nil, httperrors.NewBadRequestError("Instance backup not ready")
	}
	input, err = isp.ToInstanceCreateInput(input)
	if len(input.Disks) == 0 {
		return nil, httperrors.NewInputParameterError("there are no disks in this instance backup, try another one")
	}
	return input, nil
}

func (manager *SGuestManager) ExpandBatchCreateData(
	ctx context.Context,
	userCred mcclient.TokenCredential,
	ownerId mcclient.IIdentityProvider,
	query jsonutils.JSONObject,
	data *jsonutils.JSONDict,
	index int,
) (*api.ServerCreateInput, error) {
	input, err := cmdline.FetchServerCreateInputByJSON(data)
	if err != nil {
		return nil, err
	}
	for i := range input.Networks {
		if index < len(input.Networks[i].Macs) {
			input.Networks[i].Mac = input.Networks[i].Macs[index]
		}
		if index < len(input.Networks[i].Addresses) {
			input.Networks[i].Address = input.Networks[i].Addresses[index]
		}
		if index < len(input.Networks[i].Addresses6) {
			input.Networks[i].Address6 = input.Networks[i].Addresses6[index]
		}
	}
	log.Debugf("ExpandBatchCreateData %s", jsonutils.Marshal(input))
	return input, nil
}

func (manager *SGuestManager) validateCreateData(
	ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider,
	query jsonutils.JSONObject, data *jsonutils.JSONDict) (*api.ServerCreateInput, error) {
	// TODO: 定义 api.ServerCreateInput 的 Unmarshal 函数，直接通过 data.Unmarshal(input) 解析参数
	input, err := cmdline.FetchServerCreateInputByJSON(data)
	if err != nil {
		return nil, err
	}

	if len(input.Metadata) > 20 {
		return nil, httperrors.NewInputParameterError("metdata must less then 20")
	}

	if len(input.InstanceSnapshotId) > 0 {
		inputMem := input.VmemSize
		inputCpu := input.VcpuCount
		inputInstaceType := input.InstanceType
		input, err = parseInstanceSnapshot(ctx, input)
		if err != nil {
			return nil, err
		}
		// keep input cpu mem flavor
		if inputMem > 0 {
			input.VmemSize = inputMem
		}
		if inputMem > 0 {
			input.VcpuCount = inputCpu
		}
		if len(inputInstaceType) > 0 {
			input.InstanceType = inputInstaceType
		}
	} else if len(input.InstanceBackupId) > 0 {
		inputMem := input.VmemSize
		inputCpu := input.VcpuCount
		inputInstaceType := input.InstanceType
		input, err = parseInstanceBackup(ctx, input)
		if err != nil {
			return nil, err
		}
		// keep input cpu mem flavor
		if inputMem > 0 {
			input.VmemSize = inputMem
		}
		if inputMem > 0 {
			input.VcpuCount = inputCpu
		}
		if len(inputInstaceType) > 0 {
			input.InstanceType = inputInstaceType
		}
	}

	resetPassword := true
	if input.ResetPassword != nil {
		resetPassword = *input.ResetPassword
	}

	passwd := input.Password
	if len(passwd) > 0 {
		err = seclib2.ValidatePassword(passwd)
		if err != nil {
			return nil, err
		}
		resetPassword = true
		input.ResetPassword = &resetPassword
	}

	if resetPassword && len(input.LoginAccount) > 0 {
		if len(input.LoginAccount) > 32 {
			return nil, httperrors.NewInputParameterError("login_account is longer than 32 chars")
		}
		if err := manager.ValidateNameLoginAccount(input.LoginAccount); err != nil {
			return nil, err
		}
	}

	// check group
	if len(input.InstanceGroupIds) > 0 {
		newGroupIds := make([]string, len(input.InstanceGroupIds))
		for index, id := range input.InstanceGroupIds {
			model, err := GroupManager.FetchByIdOrName(ctx, userCred, id)
			if err != nil {
				return nil, httperrors.NewResourceNotFoundError("no such group %s", id)
			}
			newGroupIds[index] = model.GetId()
		}
		// list of id or name ==> ids
		input.InstanceGroupIds = newGroupIds
	}

	// check that all image of disk is the part of guest imgae, if use guest image to create guest
	err = manager.checkGuestImage(ctx, input)
	if err != nil {
		return nil, errors.Wrap(err, "checkGuestImage")
	}

	if len(input.PreferManager) > 0 && len(input.Provider) == 0 {
		providerObj, err := CloudproviderManager.FetchById(input.PreferManager)
		if err != nil {
			return nil, errors.Wrapf(err, "zone fetch by id %s", input.PreferZone)
		}
		provider := providerObj.(*SCloudprovider)
		input.Provider = provider.Provider
	}
	if len(input.PreferZone) > 0 && len(input.Provider) == 0 {
		zoneObj, err := ZoneManager.FetchById(input.PreferZone)
		if err != nil {
			return nil, errors.Wrapf(err, "zone fetch by id %s", input.PreferZone)
		}
		zone := zoneObj.(*SZone)
		input.PreferRegion = zone.CloudregionId
	}
	if len(input.PreferRegion) > 0 && len(input.Provider) == 0 {
		regionObj, err := CloudregionManager.FetchById(input.PreferRegion)
		if err != nil {
			return nil, errors.Wrapf(err, "region fetch by id %s", input.PreferRegion)
		}
		region := regionObj.(*SCloudregion)
		input.Provider = region.Provider
	}
	if len(input.Provider) == 0 {
		input.Provider = api.CLOUD_PROVIDER_ONECLOUD
	}

	var hypervisor string
	// var rootStorageType string
	var osProf osprofile.SOSProfile
	hypervisor = input.Hypervisor
	if hypervisor != api.HYPERVISOR_POD {
		if len(input.Disks) == 0 && input.Cdrom == "" {
			return nil, httperrors.NewInputParameterError("No bootable disk information provided")
		}
		var imgProperties map[string]string
		var imgEncryptKeyId string
		var imageDiskFormat string

		if len(input.Disks) > 0 {
			diskConfig := input.Disks[0]
			diskConfig, err = parseDiskInfo(ctx, userCred, diskConfig)
			if err != nil {
				return nil, httperrors.NewInputParameterError("Invalid root image: %s", err)
			}
			input.Disks[0] = diskConfig
			imgEncryptKeyId = diskConfig.ImageEncryptKeyId
			imgProperties = diskConfig.ImageProperties
			imageDiskFormat = imgProperties[imageapi.IMAGE_DISK_FORMAT]
			if imgProperties[imageapi.IMAGE_DISK_FORMAT] == "iso" {
				return nil, httperrors.NewInputParameterError("System disk does not support iso image, please consider using cdrom parameter")
			}
		}
		if input.Cdrom != "" {
			cdromStr := input.Cdrom
			image, err := parseIsoInfo(ctx, userCred, cdromStr)
			if err != nil {
				return nil, httperrors.NewInputParameterError("parse cdrom device info error %s", err)
			}
			input.Cdrom = image.Id
			imageDiskFormat = image.DiskFormat
			if len(imgProperties) == 0 {
				imgProperties = image.Properties
			}
		}

		// check boot indexes
		bm := bitmap.NewBitMap(128)
		if input.CdromBootIndex != nil && *input.CdromBootIndex >= 0 {
			bm.Set(int64(*input.CdromBootIndex))
		}
		for i := 0; i < len(input.Disks); i++ {
			if input.Disks[i].BootIndex != nil && *input.Disks[i].BootIndex >= 0 {
				if bm.Has(int64(*input.Disks[i].BootIndex)) {
					return nil, httperrors.NewInputParameterError("duplicate boot index %d", *input.Disks[i].BootIndex)
				}
				bm.Set(int64(*input.Disks[i].BootIndex))
			}
		}

		arch := imgProperties["os_arch"]
		if strings.Contains(arch, "aarch") || strings.Contains(arch, "arm") {
			input.OsArch = apis.OS_ARCH_AARCH64
		} else if strings.Contains(arch, "riscv") {
			input.OsArch = apis.OS_ARCH_RISCV64
		}

		// enable tpm on windows 11 image
		osDist := imgProperties["os_distribution"]
		osVer := imgProperties["os_version"]
		if strings.Contains(osDist, "Windows 11") || strings.Contains(osVer, "Windows 11") {
			input.EnableTpm = true
		}

		// use uefi boot and q35 machine type on enable tpm
		if input.EnableTpm {
			input.Bios = "UEFI"
			input.Machine = api.VM_MACHINE_TYPE_Q35
		}

		if imageDiskFormat != imageapi.IMAGE_DISK_FORMAT_ISO {
			var imgSupportUEFI *bool
			var imgSupportBIOS *bool
			if desc, ok := imgProperties[imageapi.IMAGE_UEFI_SUPPORT]; ok {
				support := desc == "true"
				imgSupportUEFI = &support
			}
			if biosDesc, ok := imgProperties[imageapi.IMAGE_BIOS_SUPPORT]; ok {
				supportBIOS := biosDesc == "true"
				imgSupportBIOS = &supportBIOS
			}
			// uefi is not support set default support bios
			if imgSupportUEFI == nil || !*imgSupportUEFI {
				supportBIOS := true
				imgSupportBIOS = &supportBIOS
			}

			if apis.IsARM(input.OsArch) || apis.IsRISCV(input.OsArch) {
				// arm image supports UEFI by default
				support := true
				imgSupportUEFI = &support
			}
			switch input.Bios {
			case "UEFI":
				if imgSupportUEFI == nil || !*imgSupportUEFI {
					return nil, httperrors.NewInputParameterError("UEFI boot mode requires UEFI image")
				}
			case "BIOS":
				if imgSupportBIOS == nil || !*imgSupportBIOS {
					return nil, httperrors.NewInputParameterError("BIOS boot mode requires BIOS image")
				}
			default:
				if imgSupportUEFI != nil && *imgSupportUEFI {
					input.Bios = "UEFI"
				} else {
					input.Bios = "BIOS"
				}
			}
		} else {
			if input.Bios == "" {
				// if ISO support uefi and not specified boot mode
				// set default boot mode uefi
				if desc, ok := imgProperties[imageapi.IMAGE_UEFI_SUPPORT]; ok && desc == "true" {
					input.Bios = "UEFI"
				}
			}

		}

		if len(imgProperties) == 0 {
			imgProperties = map[string]string{"os_type": "Linux"}
		}
		input.DisableUsbKbd = imgProperties[imageapi.IMAGE_DISABLE_USB_KBD] == "true"
		imgIsWindows := imgProperties[imageapi.IMAGE_OS_TYPE] == "Windows"

		hasGpuVga := func() bool {
			for i := 0; i < len(input.IsolatedDevices); i++ {
				if input.IsolatedDevices[i].DevType == api.GPU_VGA_TYPE {
					return true
				}
			}
			return false
		}()
		if imgIsWindows && hasGpuVga && input.Bios != "UEFI" {
			return nil, httperrors.NewInputParameterError("Windows use gpu vga requires UEFI image")
		}

		if vdi, ok := imgProperties[imageapi.IMAGE_VDI_PROTOCOL]; ok && len(vdi) > 0 && len(input.Vdi) == 0 {
			input.Vdi = vdi
		}

		if input.EncryptKeyId == nil && len(imgEncryptKeyId) > 0 {
			input.EncryptKeyId = &imgEncryptKeyId
		}
		if input.EncryptKeyId != nil || input.EncryptKeyNew != nil {
			input.EncryptedResourceCreateInput, err = manager.SEncryptedResourceManager.ValidateCreateData(ctx, userCred, ownerId, query, input.EncryptedResourceCreateInput)
			if err != nil {
				return nil, errors.Wrap(err, "SEncryptedResourceManager.ValidateCreateData")
			}
			if len(imgEncryptKeyId) > 0 {
				if imgEncryptKeyId != *input.EncryptKeyId {
					return nil, errors.Wrap(httperrors.ErrConflict, "encryption key inconsist with image")
				}
			}
		}

		osType := input.OsType
		osProf, err = osprofile.GetOSProfileFromImageProperties(imgProperties, hypervisor)
		if err != nil {
			return nil, httperrors.NewInputParameterError("Invalid root image: %s", err)
		}

		if len(osProf.Hypervisor) > 0 && len(hypervisor) == 0 {
			hypervisor = osProf.Hypervisor
			input.Hypervisor = hypervisor
		}
		if len(osProf.OSType) > 0 && len(osType) == 0 {
			osType = osProf.OSType
			input.OsType = osType
		}
		input.OsProfile = jsonutils.Marshal(osProf)
	}

	input, err = ValidateScheduleCreateData(ctx, userCred, input, hypervisor)
	if err != nil {
		return nil, err
	}

	optionSystemHypervisor := []string{api.HYPERVISOR_KVM, api.HYPERVISOR_ESXI, api.HYPERVISOR_POD}

	if !utils.IsInStringArray(input.Hypervisor, optionSystemHypervisor) && len(input.Disks[0].ImageId) == 0 && len(input.Disks[0].SnapshotId) == 0 && input.Cdrom == "" {
		return nil, httperrors.NewBadRequestError("Miss operating system???")
	}

	if input.Hypervisor == api.HYPERVISOR_KVM {
		if input.IsDaemon == nil && options.Options.SetKVMServerAsDaemonOnCreate {
			setDaemon := true
			input.IsDaemon = &setDaemon
		}
	}

	hypervisor = input.Hypervisor
	driver, err := GetDriver(hypervisor, input.Provider)
	if err != nil {
		return nil, err
	}
	if hypervisor != api.HYPERVISOR_POD {
		// support sku here
		var sku *SServerSku
		skuName := input.InstanceType
		if len(skuName) > 0 {
			sku, err = ServerSkuManager.FetchSkuByNameAndProvider(skuName, input.Provider, true)
			if err != nil {
				return nil, err
			}

			input.InstanceType = sku.Name
			input.VmemSize = sku.MemorySizeMB
			input.VcpuCount = sku.CpuCoreCount
		} else {
			vmemSize, vcpuCount, err := ValidateMemCpuData(input.VmemSize, input.VcpuCount, input.Hypervisor, input.Provider)
			if err != nil {
				return nil, err
			}

			if vmemSize == 0 {
				return nil, httperrors.NewMissingParameterError("vmem_size")
			}
			if vcpuCount == 0 {
				vcpuCount = 1
			}
			input.VmemSize = vmemSize
			input.VcpuCount = vcpuCount
		}

		dataDiskDefs := []*api.DiskConfig{}
		if sku != nil && sku.AttachedDiskCount > 0 {
			if sku.AttachedDiskSizeGB == 0 {
				return nil, httperrors.NewInputParameterError("sku %s not indicate attached disk size", sku.Name)
			}
			if len(sku.AttachedDiskType) == 0 {
				return nil, httperrors.NewInputParameterError("sku %s not indicate attached disk backend", sku.Name)
			}
			for i := 0; i < sku.AttachedDiskCount; i += 1 {
				dataDisk := &api.DiskConfig{
					SizeMb:  sku.AttachedDiskSizeGB * 1024,
					Backend: strings.ToLower(sku.AttachedDiskType),
				}
				dataDiskDefs = append(dataDiskDefs, dataDisk)
			}
		}

		// start from data disk
		disks := input.Disks
		for idx := 1; idx < len(disks); idx += 1 {
			dataDiskDefs = append(dataDiskDefs, disks[idx])
		}

		rootDiskConfig, err := parseDiskInfo(ctx, userCred, disks[0])
		if err != nil {
			return nil, httperrors.NewGeneralError(err) // should no error
		}
		// validate root disk config
		{
			if rootDiskConfig.NVMEDevice != nil {
				return nil, httperrors.NewBadRequestError("NVMe device can't assign as root disk")
			}
			if input.ResourceType != api.HostResourceTypePrepaidRecycle {
				if len(rootDiskConfig.Backend) == 0 {
					defaultStorageType, _ := data.GetString("default_storage_type")
					if len(defaultStorageType) > 0 {
						rootDiskConfig.Backend = defaultStorageType
					} else {
						rootDiskConfig.Backend = driver.GetDefaultSysDiskBackend()
					}
				}
				sysMinDiskMB := driver.GetMinimalSysDiskSizeGb() * 1024
				if rootDiskConfig.SizeMb != api.DISK_SIZE_AUTOEXTEND && rootDiskConfig.SizeMb < sysMinDiskMB {
					rootDiskConfig.SizeMb = sysMinDiskMB
				}
			}
			if len(rootDiskConfig.Driver) == 0 {
				rootDiskConfig.Driver = osProf.DiskDriver
			}
			log.Debugf("ROOT DISK: %#v", rootDiskConfig)
			input.Disks[0] = rootDiskConfig
			if sku != nil {
				if len(rootDiskConfig.OsArch) > 0 && len(sku.CpuArch) > 0 {
					if !strings.Contains(rootDiskConfig.OsArch, sku.CpuArch) {
						return nil, httperrors.NewConflictError("root disk image(%s) and sku(%s) architecture mismatch", rootDiskConfig.OsArch, sku.CpuArch)
					}
				}
			}
		}

		for i := 0; i < len(dataDiskDefs); i += 1 {
			diskConfig, err := parseDiskInfo(ctx, userCred, dataDiskDefs[i])
			if err != nil {
				return nil, httperrors.NewInputParameterError("parse disk description error %s", err)
			}
			if diskConfig.DiskType == api.DISK_TYPE_SYS {
				log.Warningf("Snapshot error: disk index %d > 0 but disk type is %s", i+1, api.DISK_TYPE_SYS)
				diskConfig.DiskType = api.DISK_TYPE_DATA
			}
			if len(diskConfig.Backend) == 0 {
				diskConfig.Backend = rootDiskConfig.Backend
			}
			if len(diskConfig.Driver) == 0 {
				diskConfig.Driver = osProf.DiskDriver
			}
			if diskConfig.NVMEDevice != nil {
				if input.Backup {
					return nil, httperrors.NewBadRequestError("Cannot create backup with isolated device")
				}
				devConfig, err := IsolatedDeviceManager.parseDeviceInfo(userCred, diskConfig.NVMEDevice)
				if err != nil {
					return nil, httperrors.NewInputParameterError("parse isolated device description error %s", err)
				}
				err = IsolatedDeviceManager.isValidNVMEDeviceInfo(devConfig)
				if err != nil {
					return nil, err
				}
				diskConfig.NVMEDevice = devConfig
				diskConfig.Driver = api.DISK_DRIVER_VFIO
				diskConfig.Backend = api.STORAGE_NVME_PT
			}

			input.Disks[i+1] = diskConfig
		}

		if len(input.Duration) > 0 {
			if input.ResourceType == api.HostResourceTypePrepaidRecycle {
				return nil, httperrors.NewConflictError("cannot create prepaid server on prepaid resource type")
			}

			billingCycle, err := billing.ParseBillingCycle(input.Duration)
			if err != nil {
				return nil, httperrors.NewInputParameterError("invalid duration %s", input.Duration)
			}

			if input.BillingType == billing_api.BILLING_TYPE_POSTPAID {
				if !driver.IsSupportPostpaidExpire() {
					return nil, httperrors.NewBadRequestError("guest %s unsupport postpaid expire", hypervisor)
				}
			} else {
				if !driver.IsSupportedBillingCycle(billingCycle) {
					return nil, httperrors.NewInputParameterError("unsupported duration %s", input.Duration)
				}
			}

			if len(input.BillingType) == 0 {
				input.BillingType = billing_api.BILLING_TYPE_PREPAID
			}
			input.BillingCycle = billingCycle.String()
			input.Duration = billingCycle.String()
			if input.BillingType == billing_api.BILLING_TYPE_POSTPAID {
				input.ReleaseAt = billingCycle.EndAt(time.Now())
			}
		}
	}

	// HACK: if input networks is empty, add one random network config
	if len(input.Networks) == 0 {
		input.Networks = append(input.Networks, &api.NetworkConfig{Exit: false})
	}
	netArray := input.Networks
	defaultGwCnt := 0
	firstExit := -1
	for idx := 0; idx < len(netArray); idx += 1 {
		netConfig, err := parseNetworkInfo(ctx, userCred, netArray[idx])
		if err != nil {
			return nil, httperrors.NewInputParameterError("parse network description error %s", err)
		}
		err = isValidNetworkInfo(ctx, userCred, netConfig, "", "")
		if err != nil {
			return nil, err
		}
		if len(netConfig.Driver) == 0 {
			netConfig.Driver = osProf.NetDriver
		}
		if netConfig.SriovDevice != nil {
			if input.Backup {
				return nil, httperrors.NewBadRequestError("Cannot create backup with isolated device")
			}
			devConfig, err := IsolatedDeviceManager.parseDeviceInfo(userCred, netConfig.SriovDevice)
			if err != nil {
				return nil, httperrors.NewInputParameterError("parse isolated device description error %s", err)
			}
			err = IsolatedDeviceManager.isValidNicDeviceInfo(devConfig)
			if err != nil {
				return nil, err
			}
			netConfig.SriovDevice = devConfig
			netConfig.Driver = api.NETWORK_DRIVER_VFIO
		}
		secgroupIds, err := isValidSecgroups(ctx, userCred, netConfig.Secgroups)
		if err != nil {
			return nil, err
		}
		netConfig.Secgroups = secgroupIds

		netConfig.Project = ownerId.GetProjectId()
		netConfig.Domain = ownerId.GetProjectDomainId()
		if netConfig.IsDefault {
			defaultGwCnt++
		}
		if firstExit < 0 && netConfig.Exit {
			firstExit = idx
		}
		input.Networks[idx] = netConfig
	}
	// check default gateway
	if defaultGwCnt == 0 {
		defIdx := 0
		if firstExit >= 0 {
			// there is a exit network, make it the default
			defIdx = firstExit
		}
		// make the first nic as default
		input.Networks[defIdx].IsDefault = true
	} else if defaultGwCnt > 1 {
		return nil, errors.Wrapf(httperrors.ErrInputParameter, "more than 1 nic(%d) assigned as default gateway", defaultGwCnt)
	}

	isoDevArray := input.IsolatedDevices
	for idx := 0; idx < len(isoDevArray); idx += 1 { // .Contains(fmt.Sprintf("isolated_device.%d", idx)); idx += 1 {
		if input.Backup {
			return nil, httperrors.NewBadRequestError("Cannot create backup with isolated device")
		}
		devConfig, err := IsolatedDeviceManager.parseDeviceInfo(userCred, isoDevArray[idx])
		if err != nil {
			return nil, httperrors.NewInputParameterError("parse isolated device description error %s", err)
		}
		err = IsolatedDeviceManager.isValidDeviceInfo(devConfig)
		if err != nil {
			return nil, err
		}
		input.IsolatedDevices[idx] = devConfig
	}

	nvidiaVgpuCnt := 0
	gpuCnt := 0
	for i := 0; i < len(input.IsolatedDevices); i++ {
		if input.IsolatedDevices[i].DevType == api.LEGACY_VGPU_TYPE {
			nvidiaVgpuCnt += 1
		} else if utils.IsInStringArray(input.IsolatedDevices[i].DevType, api.VALID_GPU_TYPES) {
			gpuCnt += 1
		}
	}

	if nvidiaVgpuCnt > 1 {
		return nil, httperrors.NewBadRequestError("Nvidia vgpu count exceed > 1")
	}
	if nvidiaVgpuCnt > 0 && gpuCnt > 0 {
		return nil, httperrors.NewBadRequestError("Nvidia vgpu can't passthrough with other gpus")
	}

	keypairId := input.KeypairId
	if len(keypairId) > 0 {
		keypairObj, err := KeypairManager.FetchByIdOrName(ctx, userCred, keypairId)
		if err != nil {
			return nil, httperrors.NewResourceNotFoundError("Keypair %s not found", keypairId)
		}
		input.KeypairId = keypairObj.GetId()
	}

	secGrpIds, err := isValidSecgroups(ctx, userCred, input.Secgroups)
	if err != nil {
		return nil, err
	}
	if len(secGrpIds) > 0 {
		input.SecgroupId = secGrpIds[0]
		input.Secgroups = secGrpIds[1:]
	} else if input.SecgroupId != "" {
		secGrpId := input.SecgroupId
		secGrpObj, err := SecurityGroupManager.FetchByIdOrName(ctx, userCred, secGrpId)
		if err != nil {
			return nil, httperrors.NewResourceNotFoundError("Secgroup %s not found", secGrpId)
		}
		input.SecgroupId = secGrpObj.GetId()
	} else {
		input.SecgroupId = options.Options.GetDefaultSecurityGroupId(hypervisor)
	}

	maxSecgrpCount := driver.GetMaxSecurityGroupCount()
	if maxSecgrpCount == 0 { //esxi 不支持安全组
		input.SecgroupId = ""
		input.Secgroups = []string{}
	} else if len(input.Secgroups)+1 > maxSecgrpCount {
		return nil, httperrors.NewInputParameterError("%s shall bind up to %d security groups", hypervisor, maxSecgrpCount)
	}

	preferRegionId, _ := data.GetString("prefer_region_id")
	if err := manager.validateEip(ctx, userCred, input, preferRegionId, input.PreferManager); err != nil {
		return nil, err
	}

	/*
		TODO
		group
		for idx := 0; data.Contains(fmt.Sprintf("srvtag.%d", idx)); idx += 1 {

		}*/

	if input.ResourceType != api.HostResourceTypePrepaidRecycle {
		input, err = driver.ValidateCreateData(ctx, userCred, input)
		if err != nil {
			return nil, err
		}
	}

	input.VirtualResourceCreateInput, err = manager.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.VirtualResourceCreateInput)
	if err != nil {
		return nil, err
	}
	name := input.Name
	if len(name) == 0 {
		name = input.GenerateName
	}
	input.HostnameInput, err = manager.SHostnameResourceBaseManager.ValidateHostname(name, input.OsType, input.HostnameInput)
	if err != nil {
		return nil, err
	}

	// validate UserData
	if err := userdata.ValidateUserdata(input.UserData, input.OsType); err != nil {
		return nil, httperrors.NewInputParameterError("Invalid userdata: %v", err)
	}

	// validate KickstartConfig
	if input.KickstartConfig != nil && input.KickstartConfig.IsEnabled() {
		if err := validateKickstartConfig(input.KickstartConfig); err != nil {
			return nil, httperrors.NewInputParameterError("Invalid kickstart config: %v", err)
		}
	}

	err = manager.ValidatePolicyDefinitions(ctx, userCred, ownerId, query, input)
	if err != nil {
		return nil, err
	}

	input.ProjectId = ownerId.GetProjectId()
	input.ProjectDomainId = ownerId.GetProjectDomainId()
	return input, nil
}

func (manager *SGuestManager) ValidatePolicyDefinitions(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input *api.ServerCreateInput) error {
	definitions, err := PolicyDefinitionManager.GetAvailablePolicyDefinitions(ctx, userCred)
	if err != nil {
		return httperrors.NewGeneralError(err)
	}
	for i := range definitions {
		switch definitions[i].Category {
		case api.POLICY_DEFINITION_CATEGORY_CLOUDREGION:
			if len(input.PreferRegion) == 0 {
				return httperrors.NewMissingParameterError(fmt.Sprintf("policy definition %s require prefer_region_id parameter", definitions[i].Name))
			}
			if definitions[i].Parameters == nil {
				return httperrors.NewPolicyDefinitionError("invalid parameters for policy definition %s", definitions[i].Name)
			}
			regionDefinitions := api.SCloudregionPolicyDefinitions{}
			definitions[i].Parameters.Unmarshal(&regionDefinitions)
			regions := []string{}
			for _, region := range regionDefinitions.Cloudregions {
				regions = append(regions, region.Id)
				regions = append(regions, region.Name)
			}
			isIn := utils.IsInStringArray(input.PreferRegion, regions)
			switch definitions[i].Condition {
			case api.POLICY_DEFINITION_CONDITION_IN:
				if !isIn {
					return httperrors.NewPolicyDefinitionError("policy definition %s require cloudregion in %s", definitions[i].Name, definitions[i].Parameters)
				}
			case api.POLICY_DEFINITION_CONDITION_NOT_IN:
				if isIn {
					return httperrors.NewPolicyDefinitionError("policy definition %s require cloudregion not in %s", definitions[i].Name, definitions[i].Parameters)
				}
			default:
				return httperrors.NewPolicyDefinitionError("invalid policy definition %s(%s) condition %s", definitions[i].Name, definitions[i].Id, definitions[i].Condition)
			}
		case api.POLICY_DEFINITION_CATEGORY_TAG:
			tags := []string{}
			if definitions[i].Parameters == nil {
				return httperrors.NewPolicyDefinitionError("invalid parameters for policy definition %s", definitions[i].Name)
			}
			definitions[i].Parameters.Unmarshal(&tags, "tags")
			metadataKeys := []string{}
			for k, _ := range input.Metadata {
				metadataKeys = append(metadataKeys, strings.TrimPrefix(k, db.USER_TAG_PREFIX))
			}
			for _, tag := range tags {
				isIn := utils.IsInStringArray(tag, metadataKeys)
				switch definitions[i].Condition {
				case api.POLICY_DEFINITION_CONDITION_CONTAINS:
					if !isIn {
						return httperrors.NewPolicyDefinitionError("policy definition %s require must contains tag %s", definitions[i].Name, tag)
					}
				case api.POLICY_DEFINITION_CONDITION_EXCEPT:
					if isIn {
						return httperrors.NewPolicyDefinitionError("policy definition %s require except tag %s", definitions[i].Name, tag)
					}
				default:
					return httperrors.NewPolicyDefinitionError("invalid policy definition %s(%s) condition %s", definitions[i].Name, definitions[i].Id, definitions[i].Condition)
				}
			}
		default:
			return httperrors.NewPolicyDefinitionError("invalid category %s for policy definition %s(%s)", definitions[i].Category, definitions[i].Name, definitions[i].Id)
		}
	}
	return nil
}

func (manager *SGuestManager) BatchCreateValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
	input, err := manager.validateCreateData(ctx, userCred, ownerId, query, data)
	if err != nil {
		return nil, err
	}
	return input.JSON(input), nil
}

// 创建虚拟机实例
func (manager *SGuestManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, oinput api.ServerCreateInput) (*jsonutils.JSONDict, error) {
	input, err := manager.validateCreateData(ctx, userCred, ownerId, query, oinput.JSON(oinput))
	if err != nil {
		return nil, err
	}
	if input.IsSystem == nil || !(*input.IsSystem) {
		err = manager.checkCreateQuota(ctx, userCred, ownerId, *input, input.Backup, 1)
		if err != nil {
			return nil, err
		}
	}

	return input.JSON(input), nil
}

func validateKickstartConfig(config *api.KickstartConfig) error {
	if config.OSType == "" {
		return httperrors.NewMissingParameterError("os_type")
	}

	if !utils.IsInStringArray(config.OSType, api.KICKSTART_VALID_OS_TYPES) {
		return httperrors.NewInputParameterError("unsupported os_type: %s, supported types: %v", config.OSType, api.KICKSTART_VALID_OS_TYPES)
	}

	// 验证配置内容和URL二选一
	if config.Config == "" && config.ConfigURL == "" {
		return httperrors.NewInputParameterError("either config or config_url must be provided")
	}

	if config.Config != "" && config.ConfigURL != "" {
		return httperrors.NewInputParameterError("config and config_url cannot be both provided, choose one")
	}

	if config.Config != "" {
		const maxConfigSize = 64 * 1024
		if len(config.Config) > maxConfigSize {
			return httperrors.NewInputParameterError("config content too large: %d bytes, maximum allowed: %d bytes", len(config.Config), maxConfigSize)
		}
		if len(strings.TrimSpace(config.Config)) == 0 {
			return httperrors.NewInputParameterError("config content cannot be empty")
		}
	}

	if config.ConfigURL != "" {
		if len(config.ConfigURL) > 2048 {
			return httperrors.NewInputParameterError("config URL too long: %d characters, maximum allowed: 2048", len(config.ConfigURL))
		}
		if strings.TrimSpace(config.ConfigURL) == "" {
			return httperrors.NewInputParameterError("config URL cannot be empty")
		}

		parsedURL, err := url.Parse(config.ConfigURL)
		if err != nil {
			return httperrors.NewInputParameterError("invalid URL format: %v", err)
		}
		if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
			return httperrors.NewInputParameterError("invalid URL scheme: %s, only http and https are allowed", parsedURL.Scheme)
		}
		if parsedURL.Host == "" {
			return httperrors.NewInputParameterError("URL must specify a host")
		}

		if err := checkKickstartURLContentSize(config.ConfigURL); err != nil {
			return httperrors.NewInputParameterError("URL content validation failed: %v", err)
		}
	}

	// 设置默认值
	if config.Enabled == nil {
		enabled := true
		config.Enabled = &enabled
	}

	if config.MaxRetries <= 0 {
		config.MaxRetries = 3
	}

	if config.TimeoutMinutes <= 0 {
		config.TimeoutMinutes = 60
	}

	return nil
}

// determineKickstartType determines kickstart type based on config content
func determineKickstartType(config *api.KickstartConfig) string {
	if config.Config != "" {
		return api.KICKSTART_TYPE_CONTENT
	}
	return api.KICKSTART_TYPE_URL
}

func checkKickstartURLContentSize(configURL string) error {
	const maxURLContentSize = 64 * 1024
	const requestTimeout = 10 * time.Second

	client := &http.Client{Timeout: requestTimeout}

	req, err := http.NewRequest("HEAD", configURL, nil)
	if err != nil {
		return fmt.Errorf("failed to create request: %v", err)
	}

	resp, err := client.Do(req)
	if err != nil {
		log.Warningf("Failed to check URL content size for %s: %v", configURL, err)
		return nil
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("URL returned status code: %d", resp.StatusCode)
	}

	contentLengthStr := resp.Header.Get("Content-Length")
	if contentLengthStr != "" {
		contentLength, err := strconv.ParseInt(contentLengthStr, 10, 64)
		if err != nil {
			log.Warningf("Failed to parse Content-Length header: %v", err)
			return nil
		}

		if contentLength > maxURLContentSize {
			return fmt.Errorf("URL content too large: %d bytes, maximum allowed: %d bytes", contentLength, maxURLContentSize)
		}

		log.Infof("URL content size validated: %d bytes", contentLength)
	} else {
		log.Warningf("URL %s does not provide Content-Length header, size validation skipped", configURL)
	}

	return nil
}

func (manager *SGuestManager) validateEip(ctx context.Context, userCred mcclient.TokenCredential, input *api.ServerCreateInput,
	preferRegionId string, preferManagerId string) error {
	driver, err := GetDriver(input.Hypervisor, input.Provider)
	if err != nil {
		return err
	}
	if input.PublicIpBw > 0 {

		if !driver.IsSupportPublicIp() {
			return httperrors.NewNotImplementedError("public ip not supported for %s", input.Hypervisor)
		}
		if len(input.PublicIpChargeType) == 0 {
			input.PublicIpChargeType = billing_api.TNetChargeType(cloudprovider.ElasticipChargeTypeByTraffic)
		}
		if !utils.IsInStringArray(string(input.PublicIpChargeType), []string{
			string(cloudprovider.ElasticipChargeTypeByTraffic),
			string(cloudprovider.ElasticipChargeTypeByBandwidth),
		}) {
			return httperrors.NewInputParameterError("invalid public_ip_charge_type %s", input.PublicIpChargeType)
		}
		return nil
	}
	eipStr := input.Eip
	eipBw := input.EipBw
	if len(eipStr) > 0 || eipBw > 0 {
		if !driver.IsSupportEip() {
			return httperrors.NewNotImplementedError("eip not supported for %s", input.Hypervisor)
		}
		if len(eipStr) > 0 {
			eipObj, err := ElasticipManager.FetchByIdOrName(ctx, userCred, eipStr)
			if err != nil {
				if err == sql.ErrNoRows {
					return httperrors.NewResourceNotFoundError2(ElasticipManager.Keyword(), eipStr)
				} else {
					return httperrors.NewGeneralError(err)
				}
			}

			eip := eipObj.(*SElasticip)
			if eip.Status != api.EIP_STATUS_READY {
				return httperrors.NewInvalidStatusError("eip %s status invalid %s", eipStr, eip.Status)
			}
			if eip.IsAssociated() {
				return httperrors.NewResourceBusyError("eip %s has been associated", eipStr)
			}
			input.Eip = eipObj.GetId()

			eipCloudprovider := eip.GetCloudprovider()
			if eipCloudprovider != nil {
				if len(preferManagerId) > 0 && preferManagerId != eipCloudprovider.Id {
					return httperrors.NewConflictError("cannot assoicate with eip %s: different cloudprovider", eipStr)
				}
				input.PreferManager = eipCloudprovider.Id
			}

			eipRegion, err := eip.GetRegion()
			if err != nil {
				return httperrors.NewGeneralError(errors.Wrapf(err, "eip.GetRegion"))
			}
			// preferRegionId, _ := data.GetString("prefer_region_id")
			if len(preferRegionId) > 0 && preferRegionId != eipRegion.Id {
				return httperrors.NewConflictError("cannot assoicate with eip %s: different region", eipStr)
			}
			input.PreferRegion = eipRegion.Id
		} else {
			// create new eip
		}
	}
	return nil
}

func (self *SGuest) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
	self.SVirtualResourceBase.PostUpdate(ctx, userCred, query, data)
	if len(self.ExternalId) > 0 && (data.Contains("name") || data.Contains("__meta__") || data.Contains("description")) || data.Contains("hostname") {
		err := self.StartRemoteUpdateTask(ctx, userCred, false, "")
		if err != nil {
			log.Errorf("StartRemoteUpdateTask fail: %s", err)
		}
	}
	if port, err := data.Int("ssh_port"); err != nil {
		err := self.SetSshPort(ctx, userCred, int(port))
		if err != nil {
			log.Errorf("unable to set sshport for guest %s", self.GetId())
		}
	}
}

func (manager *SGuestManager) checkCreateQuota(
	ctx context.Context,
	userCred mcclient.TokenCredential,
	ownerId mcclient.IIdentityProvider,
	input api.ServerCreateInput,
	hasBackup bool,
	count int,
) error {
	req, regionReq := getGuestResourceRequirements(ctx, userCred, input, ownerId, count, hasBackup)
	log.Debugf("computeQuota: %s", jsonutils.Marshal(req))
	log.Debugf("regionQuota: %s", jsonutils.Marshal(regionReq))

	err := quotas.CheckSetPendingQuota(ctx, userCred, &req)
	if err != nil {
		return errors.Wrap(err, "quotas.CheckSetPendingQuota")
	}
	err = quotas.CheckSetPendingQuota(ctx, userCred, &regionReq)
	if err != nil {
		return errors.Wrap(err, "quotas.CheckSetPendingQuota")
	}
	return nil
}

func (self *SGuest) checkUpdateQuota(ctx context.Context, userCred mcclient.TokenCredential, vcpuCount int, vmemSize int) (quotas.IQuota, error) {
	req := SQuota{}

	if vcpuCount > 0 && vcpuCount > int(self.VcpuCount) {
		req.Cpu = vcpuCount - int(self.VcpuCount)
	}

	if vmemSize > 0 && vmemSize > self.VmemSize {
		req.Memory = vmemSize - self.VmemSize
	}

	keys, err := self.GetQuotaKeys()
	if err != nil {
		return nil, errors.Wrap(err, "self.GetQuotaKeys")
	}
	req.SetKeys(keys)
	err = quotas.CheckSetPendingQuota(ctx, userCred, &req)
	if err != nil {
		return nil, errors.Wrap(err, "quotas.CheckSetPendingQuota")
	}

	return &req, nil
}

func getGuestResourceRequirements(
	ctx context.Context,
	userCred mcclient.TokenCredential,
	input api.ServerCreateInput,
	ownerId mcclient.IIdentityProvider,
	count int,
	hasBackup bool,
) (SQuota, SRegionQuota) {
	vcpuCount := input.VcpuCount
	if vcpuCount == 0 {
		vcpuCount = 1
	}

	vmemSize := input.VmemSize

	diskSize := 0

	for _, diskConfig := range input.Disks {
		if diskConfig.DiskId != "" {
			// disk has been created, ignore resource requirement
			continue
		}
		diskSize += diskConfig.SizeMb
	}

	devCount := len(input.IsolatedDevices)

	eNicCnt := 0
	iNicCnt := 0
	eBw := 0
	iBw := 0
	for _, netConfig := range input.Networks {
		if IsExitNetworkInfo(ctx, userCred, netConfig) {
			eNicCnt += 1
			eBw += netConfig.BwLimit
		} else {
			iNicCnt += 1
			iBw += netConfig.BwLimit
		}
		if netConfig.SriovDevice != nil {
			devCount += 1
		}
	}
	if hasBackup {
		vcpuCount = vcpuCount * 2
		vmemSize = vmemSize * 2
		diskSize = diskSize * 2
	}

	eipCnt := 0
	eipBw := input.EipBw
	if eipBw > 0 {
		eipCnt = 1
	}

	req := SQuota{
		Count:          count,
		Cpu:            int(vcpuCount) * count,
		Memory:         int(vmemSize) * count,
		Storage:        diskSize * count,
		IsolatedDevice: devCount * count,
	}
	regionReq := SRegionQuota{
		Port:  iNicCnt * count,
		Eport: eNicCnt * count,
		//Bw:    iBw * count,
		//Ebw:   eBw * count,
		Eip: eipCnt * count,
	}
	keys, _ := serverCreateInput2ComputeQuotaKeys(input, ownerId)
	req.SetKeys(keys)
	regionReq.SetKeys(keys.SRegionalCloudResourceKeys)
	return req, regionReq
}

func (guest *SGuest) getGuestBackupResourceRequirements(ctx context.Context, userCred mcclient.TokenCredential) SQuota {
	guestDisksSize := guest.getDiskSize()
	return SQuota{
		Count:   1,
		Cpu:     int(guest.VcpuCount),
		Memory:  guest.VmemSize,
		Storage: guestDisksSize,
	}
}

func (guest *SGuest) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
	guest.SVirtualResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
	tags := []string{"cpu_bound", "io_bound", "io_hardlimit"}
	appTags := make([]string, 0)
	for _, tag := range tags {
		if data.Contains(tag) {
			appTags = append(appTags, tag)
		}
	}
	guest.setApptags(ctx, appTags, userCred)
	guest.SetCreateParams(ctx, userCred, data)
	osProfileJson, _ := data.Get("__os_profile__")
	if osProfileJson != nil {
		guest.setOSProfile(ctx, userCred, osProfileJson)
	}

	if jsonutils.QueryBoolean(data, api.VM_METADATA_ENABLE_MEMCLEAN, false) {
		guest.SetMetadata(ctx, api.VM_METADATA_ENABLE_MEMCLEAN, "true", userCred)
	}
	if jsonutils.QueryBoolean(data, api.VM_METADATA_ENABLE_TPM, false) {
		guest.SetMetadata(ctx, api.VM_METADATA_ENABLE_TPM, "true", userCred)
	}
	if jsonutils.QueryBoolean(data, imageapi.IMAGE_DISABLE_USB_KBD, false) {
		guest.SetMetadata(ctx, imageapi.IMAGE_DISABLE_USB_KBD, "true", userCred)
	}
	matcherJson, _ := data.Get(api.BAREMETAL_SERVER_METATA_ROOT_DISK_MATCHER)
	if matcherJson != nil {
		guest.SetMetadata(ctx, api.BAREMETAL_SERVER_METATA_ROOT_DISK_MATCHER, matcherJson, userCred)
	}

	userData, _ := data.GetString("user_data")
	if len(userData) > 0 {
		guest.setUserData(ctx, userCred, userData)
	}

	if guest.Hypervisor == api.HYPERVISOR_ESXI {
		schedtags := []api.SchedtagConfig{}
		data.Unmarshal(&schedtags, "schedtags")

		for _, tag := range schedtags {
			if tag.ResourceType != HostManager.KeywordPlural() {
				continue
			}
			meta := db.SMetadata{}
			db.Metadata.Query().
				Equals("obj_type", SchedtagManager.Keyword()).
				Equals("obj_id", tag.Id).
				Equals("key", cloudprovider.METADATA_POOL_ID).First(&meta)
			if len(meta.Value) > 0 {
				db.Update(guest, func() error {
					guest.ResourcePool = meta.Value
					return nil
				})
			}
		}
	}

	// set kickstart metadata
	kickstartConfigJson, _ := data.Get("kickstart_config")
	if kickstartConfigJson != nil {
		kickstartConfig := &api.KickstartConfig{}
		if err := kickstartConfigJson.Unmarshal(kickstartConfig); err != nil {
			log.Errorf("unmarshal kickstart config fail: %s", err)
		} else if kickstartConfig.IsEnabled() {
			if err := guest.SetKickstartConfig(ctx, kickstartConfig, userCred); err != nil {
				log.Errorf("Failed to set kickstart config for guest %s: %v", guest.Name, err)
			} else {
				//if err := guest.SetKickstartStatus(ctx, api.VM_KICKSTART_PENDING, userCred); err != nil {
				//	log.Errorf("Failed to set kickstart status for guest %s: %v", guest.Name, err)
				//}
				if err := guest.SetMetadata(ctx, api.VM_METADATA_KICKSTART_COMPLETED_FLAG, "false", userCred); err != nil {
					log.Errorf("Failed to set kickstart completed flag for guest %s: %v", guest.Name, err)
				}

				// Determine and set kickstart type based on config
				kickstartType := determineKickstartType(kickstartConfig)

				if err := guest.SetKickstartType(ctx, kickstartType, userCred); err != nil {
					log.Errorf("Failed to set kickstart type for guest %s: %v", guest.Name, err)
				}

				log.Debugf("Successfully set kickstart config for guest %s with OS type %s", guest.Name, kickstartConfig.OSType)
			}
		}
	}

	input := struct {
		PreferZone      string
		PreferRegion    string
		PreferManagerId string
		Provider        string
	}{}
	data.Unmarshal(&input)

	if len(input.PreferManagerId) > 0 && len(input.Provider) == 0 {
		manObj, err := CloudproviderManager.FetchById(input.PreferManagerId)
		if err == nil {
			man := manObj.(*SCloudprovider)
			input.Provider = man.Provider
		}
	}
	if len(input.PreferZone) > 0 && len(input.Provider) == 0 {
		zoneObj, err := ZoneManager.FetchById(input.PreferZone)
		if err == nil {
			zone := zoneObj.(*SZone)
			input.PreferRegion = zone.CloudregionId
		}
	}
	if len(input.PreferRegion) > 0 && len(input.Provider) == 0 {
		regionObj, err := CloudregionManager.FetchById(input.PreferRegion)
		if err == nil {
			region := regionObj.(*SCloudregion)
			input.Provider = region.Provider
		}
	}
	if len(input.Provider) == 0 {
		input.Provider = api.CLOUD_PROVIDER_ONECLOUD
	}
	drv, _ := GetDriver(guest.Hypervisor, input.Provider)
	if drv != nil && drv.GetMaxSecurityGroupCount() > 0 {
		secgroups, _ := jsonutils.GetStringArray(data, "secgroups")
		for _, secgroupId := range secgroups {
			if secgroupId != guest.SecgrpId {
				gs := SGuestsecgroup{}
				gs.SecgroupId = secgroupId
				gs.GuestId = guest.Id
				GuestsecgroupManager.TableSpec().Insert(ctx, &gs)
			}
		}
	} else {
		db.Update(guest, func() error {
			guest.SecgrpId = ""
			return nil
		})
	}
}

func (guest *SGuest) setApptags(ctx context.Context, appTags []string, userCred mcclient.TokenCredential) {
	err := guest.SetMetadata(ctx, api.VM_METADATA_APP_TAGS, strings.Join(appTags, ","), userCred)
	if err != nil {
		log.Errorln(err)
	}
}

func (guest *SGuest) SetCreateParams(ctx context.Context, userCred mcclient.TokenCredential, data jsonutils.JSONObject) {
	// delete deploy files info
	createParams := data.(*jsonutils.JSONDict).CopyExcludes("deploy_configs")
	err := guest.SetMetadata(ctx, api.VM_METADATA_CREATE_PARAMS, createParams.String(), userCred)
	if err != nil {
		log.Errorf("Server %s SetCreateParams: %v", guest.Name, err)
	}
}

func (guest *SGuest) GetCreateParams(ctx context.Context, userCred mcclient.TokenCredential) (*api.ServerCreateInput, error) {
	input := new(api.ServerCreateInput)
	data := guest.GetMetadataJson(ctx, api.VM_METADATA_CREATE_PARAMS, userCred)
	if data == nil {
		return nil, fmt.Errorf("Not found %s %s in metadata", guest.Name, api.VM_METADATA_CREATE_PARAMS)
	}
	err := data.Unmarshal(input)
	return input, err
}

func (manager *SGuestManager) SetPropertiesWithInstanceSnapshot(
	ctx context.Context, userCred mcclient.TokenCredential, ispId string, items []db.IModel,
) {
	misp, err := InstanceSnapshotManager.FetchById(ispId)
	if err == nil {
		isp := misp.(*SInstanceSnapshot)
		for i := 0; i < len(items); i++ {
			guest := items[i].(*SGuest)
			if isp.ServerMetadata != nil {
				metadata := make(map[string]interface{}, 0)
				isp.ServerMetadata.Unmarshal(metadata)
				if passwd, ok := metadata["passwd"]; ok {
					delete(metadata, "passwd")
					metadata["login_key"], _ = utils.EncryptAESBase64(guest.Id, passwd.(string))
				}
				metadata[api.BASE_INSTANCE_SNAPSHOT_ID] = isp.Id
				guest.SetAllMetadata(ctx, metadata, userCred)
			}
		}
	}
}

func (manager *SGuestManager) OnCreateComplete(ctx context.Context, items []db.IModel, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data []jsonutils.JSONObject) {
	input := api.ServerCreateInput{}
	data[0].Unmarshal(&input)
	if len(input.InstanceSnapshotId) > 0 {
		manager.SetPropertiesWithInstanceSnapshot(ctx, userCred, input.InstanceSnapshotId, items)
	}
	pendingUsage, pendingRegionUsage := getGuestResourceRequirements(ctx, userCred, input, ownerId, len(items), input.Backup)
	err := RunBatchCreateTask(ctx, items, userCred, data, pendingUsage, pendingRegionUsage, "GuestBatchCreateTask", input.ParentTaskId)
	if err != nil {
		for i := range items {
			guest := items[i].(*SGuest)
			guest.SetStatus(ctx, userCred, api.VM_CREATE_FAILED, err.Error())
		}
	}
}

func (guest *SGuest) GetGroups() []SGroupguest {
	guestgroups := make([]SGroupguest, 0)
	q := GroupguestManager.Query().Equals("guest_id", guest.Id)
	err := db.FetchModelObjects(GroupguestManager, q, &guestgroups)
	if err != nil {
		log.Errorf("GetGroups fail %s", err)
		return nil
	}
	return guestgroups
}

func (self *SGuest) getBandwidth(isExit bool) int {
	bw := 0
	networks, err := self.GetNetworks("")
	if err != nil {
		return bw
	}
	if networks != nil && len(networks) > 0 {
		for i := 0; i < len(networks); i += 1 {
			net, _ := networks[i].GetNetwork()
			if networks[i].IsExit(net) == isExit {
				bw += networks[i].getBandwidth(net, nil)
			}
		}
	}
	return bw
}

func (self *SGuest) getExtBandwidth() int {
	return self.getBandwidth(true)
}

func (self *SGuestManager) GetMetadataHiddenKeys() []string {
	return []string{
		api.VM_METADATA_CREATE_PARAMS,
	}
}

func (manager *SGuestManager) ListItemExportKeys(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, keys stringutils2.SSortedStrings) (*sqlchemy.SQuery, error) {
	var err error
	q, err = manager.SVirtualResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
	if err != nil {
		return nil, err
	}

	// exportKeys, _ := query.GetString("export_keys")
	// keys := strings.Split(exportKeys, ",")
	// guest_id as filter key
	if keys.Contains("ips") {
		guestIpsQuery := GuestnetworkManager.Query("guest_id").GroupBy("guest_id")
		guestIpsQuery.AppendField(sqlchemy.GROUP_CONCAT("concat_ip_addr", guestIpsQuery.Field("ip_addr")))
		ipsSubQuery := guestIpsQuery.SubQuery()
		q.LeftJoin(ipsSubQuery, sqlchemy.Equals(q.Field("id"), ipsSubQuery.Field("guest_id")))
		q.AppendField(ipsSubQuery.Field("concat_ip_addr"))
	}

	if keys.Contains("disk") {
		guestDisksQuery := GuestdiskManager.Query("guest_id", "disk_id").GroupBy("guest_id")
		diskQuery := DiskManager.Query("id", "disk_size").SubQuery()
		guestDisksQuery.Join(diskQuery, sqlchemy.Equals(diskQuery.Field("id"), guestDisksQuery.Field("disk_id")))
		guestDisksQuery.AppendField(sqlchemy.SUM("disk_size", diskQuery.Field("disk_size")))
		guestDisksSubQuery := guestDisksQuery.SubQuery()
		q.LeftJoin(guestDisksSubQuery, sqlchemy.Equals(q.Field("id"), guestDisksSubQuery.Field("guest_id")))
		q.AppendField(guestDisksSubQuery.Field("disk_size"))
	}
	if keys.Contains("eip") {
		eipsQuery := ElasticipManager.Query("associate_id", "ip_addr").Equals("associate_type", "server").GroupBy("associate_id")
		eipsSubQuery := eipsQuery.SubQuery()
		q.LeftJoin(eipsSubQuery, sqlchemy.Equals(q.Field("id"), eipsSubQuery.Field("associate_id")))
		q.AppendField(eipsSubQuery.Field("ip_addr", "eip"))
	}

	if keys.ContainsAny(manager.SHostResourceBaseManager.GetExportKeys()...) {
		q, err = manager.SHostResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
		if err != nil {
			return nil, errors.Wrap(err, "SHostResourceBaseManager.ListItemExportKeys")
		}
	}

	return q, nil
}

func (manager *SGuestManager) GetExportExtraKeys(ctx context.Context, keys stringutils2.SSortedStrings, rowMap map[string]string) *jsonutils.JSONDict {
	res := manager.SVirtualResourceBaseManager.GetExportExtraKeys(ctx, keys, rowMap)
	// exportKeys, _ := query.GetString("export_keys")
	// keys := strings.Split(exportKeys, ",")
	if ips, ok := rowMap["concat_ip_addr"]; ok && len(ips) > 0 {
		res.Set("ips", jsonutils.NewString(ips))
	}
	if eip, ok := rowMap["eip"]; ok && len(eip) > 0 {
		res.Set("eip", jsonutils.NewString(eip))
	}
	if disk, ok := rowMap["disk_size"]; ok {
		res.Set("disk", jsonutils.NewString(disk))
	}
	if host, ok := rowMap["host"]; ok && len(host) > 0 {
		res.Set("host", jsonutils.NewString(host))
	}
	if zone, ok := rowMap["zone"]; ok && len(zone) > 0 {
		res.Set("zone", jsonutils.NewString(zone))
	}
	if region, ok := rowMap["region"]; ok && len(region) > 0 {
		res.Set("region", jsonutils.NewString(region))
	}
	if manager, ok := rowMap["manager"]; ok && len(manager) > 0 {
		res.Set("manager", jsonutils.NewString(manager))
	}
	if keys.Contains("tenant") {
		if projectId, ok := rowMap["tenant_id"]; ok {
			tenant, err := db.TenantCacheManager.FetchTenantById(ctx, projectId)
			if err == nil {
				res.Set("tenant", jsonutils.NewString(tenant.GetName()))
			}
		}
	}
	if keys.Contains("os_distribution") {
		if osType, ok := rowMap["os_type"]; ok {
			res.Set("os_distribution", jsonutils.NewString(osType))
		}
	}
	return res
}

func (self *SGuest) getNetworksDetails() string {
	guestnets, err := self.GetNetworks("")
	if err != nil {
		return ""
	}
	var buf bytes.Buffer
	for _, nic := range guestnets {
		buf.WriteString(nic.GetDetailedString())
		buf.WriteString("\n")
	}
	return buf.String()
}

func (self *SGuest) GetCdrom() *SGuestcdrom {
	return self.getCdrom(false, 0)
}

func (self *SGuest) GetCdromByOrdinal(ordinal int64) *SGuestcdrom {
	return self.getCdrom(false, ordinal)
}

func (self *SGuest) getCdrom(create bool, ordinal int64) *SGuestcdrom {
	cdrom := SGuestcdrom{}
	cdrom.SetModelManager(GuestcdromManager, &cdrom)

	err := GuestcdromManager.Query().Equals("id", self.Id).Equals("ordinal", ordinal).First(&cdrom)
	if err != nil {
		if err == sql.ErrNoRows {
			if create {
				cdrom.Id = self.Id
				cdrom.Ordinal = int(ordinal)
				err = GuestcdromManager.TableSpec().Insert(context.TODO(), &cdrom)
				if err != nil {
					log.Errorf("insert cdrom fail %s", err)
					return nil
				}
				return &cdrom
			} else {
				return nil
			}
		} else {
			log.Errorf("getCdrom query fail %s", err)
			return nil
		}
	} else {
		return &cdrom
	}
}

func (self *SGuest) getCdroms() ([]SGuestcdrom, error) {
	cdroms := make([]SGuestcdrom, 0)
	q := GuestcdromManager.Query().Equals("id", self.Id)
	err := db.FetchModelObjects(GuestcdromManager, q, &cdroms)
	if err != nil {
		return nil, errors.Wrapf(err, "db.FetchModelObjects")
	}
	return cdroms, nil
}

func (self *SGuest) getFloppys() ([]SGuestfloppy, error) {
	floppys := make([]SGuestfloppy, 0)
	q := GuestFloppyManager.Query().Equals("id", self.Id)
	err := db.FetchModelObjects(GuestFloppyManager, q, &floppys)
	if err != nil {
		return nil, errors.Wrapf(err, "db.FetchModelObjects")
	}
	return floppys, nil
}

func (self *SGuest) getFloppy(create bool, ordinal int64) *SGuestfloppy {
	floppy := SGuestfloppy{}
	floppy.SetModelManager(GuestFloppyManager, &floppy)

	err := GuestFloppyManager.Query().Equals("id", self.Id).Equals("ordinal", ordinal).First(&floppy)
	if err != nil {
		if err == sql.ErrNoRows {
			if create {
				floppy.Id = self.Id
				floppy.Ordinal = int(ordinal)
				err = GuestFloppyManager.TableSpec().Insert(context.TODO(), &floppy)
				if err != nil {
					log.Errorf("insert cdrom fail %s", err)
					return nil
				}
				return &floppy
			} else {
				return nil
			}
		} else {
			log.Errorf("getFloppy query fail %s", err)
			return nil
		}
	} else {
		return &floppy
	}
}

func (self *SGuest) getKeypair() *SKeypair {
	if len(self.KeypairId) > 0 {
		keypair, _ := KeypairManager.FetchById(self.KeypairId)
		if keypair != nil {
			return keypair.(*SKeypair)
		}
	}
	return nil
}

func (self *SGuest) getKeypairName() string {
	keypair := self.getKeypair()
	if keypair != nil {
		return keypair.Name
	}
	return ""
}

func (self *SGuest) getNotifyIps() string {
	ips := self.GetRealIPs()
	vips := self.getVirtualIPs()
	if vips != nil {
		ips = append(ips, vips...)
	}
	return strings.Join(ips, ",")
}

/*
func (self *SGuest) GetRealIPs() []string {
	guestnets, err := self.GetNetworks("")
	if err != nil {
		return nil
	}
	ips := make([]string, 0)
	for _, nic := range guestnets {
		if !nic.Virtual {
			ips = append(ips, nic.IpAddr)
		}
	}
	return ips
}
*/

func (self *SGuest) IsExitOnly() bool {
	for _, ip := range self.GetRealIPs() {
		if regutils.MatchIP4Addr(ip) {
			addr, _ := netutils.NewIPV4Addr(ip)
			if !netutils.IsExitAddress(addr) {
				return false
			}
		}
	}
	return true
}

func (self *SGuest) getVirtualIPs() []string {
	ips := make([]string, 0)
	for _, guestgroup := range self.GetGroups() {
		group := guestgroup.GetGroup()
		groupnets, err := group.GetNetworks()
		if err != nil {
			continue
		}
		for _, groupnetwork := range groupnets {
			if len(groupnetwork.IpAddr) > 0 {
				ips = append(ips, groupnetwork.IpAddr)
			}
			if len(groupnetwork.Ip6Addr) > 0 {
				ips = append(ips, groupnetwork.Ip6Addr)
			}
		}
	}
	return ips
}

func (self *SGuest) GetPrivateIPs() []string {
	ips := self.GetRealIPs()
	for i := len(ips) - 1; i >= 0; i-- {
		if regutils.MatchIP4Addr(ips[i]) {
			ipAddr, err := netutils.NewIPV4Addr(ips[i])
			if err != nil {
				log.Errorf("guest %s(%s) has bad ipv4 address (%s): %v", self.Name, self.Id, ips[i], err)
				continue
			}
			if !netutils.IsPrivate(ipAddr) {
				ips = append(ips[:i], ips[i+1:]...)
			}
		}
	}
	return ips
}

func (self *SGuest) getIPs() []string {
	ips := self.GetRealIPs()
	vips := self.getVirtualIPs()
	ips = append(ips, vips...)
	/*eip, _ := self.GetEip()
	if eip != nil {
		ips = append(ips, eip.IpAddr)
	}*/
	return ips
}

func (self *SGuest) getZone() (*SZone, error) {
	host, err := self.GetHost()
	if err != nil {
		return nil, err
	}
	return host.GetZone()
}

func (self *SGuest) getRegion() (*SCloudregion, error) {
	zone, err := self.getZone()
	if err != nil {
		return nil, err
	}
	return zone.GetRegion()
}

func (self *SGuest) GetOS() string {
	if len(self.OsType) > 0 {
		return self.OsType
	}
	return self.GetMetadata(context.Background(), "os_name", nil)
}

func (self *SGuest) IsLinux() bool {
	os := self.GetOS()
	if strings.HasPrefix(strings.ToLower(os), "lin") {
		return true
	} else {
		return false
	}
}

func (self *SGuest) IsWindows() bool {
	os := self.GetOS()
	if strings.HasPrefix(strings.ToLower(os), "win") {
		return true
	} else {
		return false
	}
}

func (self *SGuest) getSecgroupJson() ([]*api.SecgroupJsonDesc, error) {
	ret := []*api.SecgroupJsonDesc{}
	secgroups, err := self.GetSecgroups()
	if err != nil {
		return nil, errors.Wrap(err, "GetSecgroups")
	}
	for _, secGrp := range secgroups {
		ret = append(ret, secGrp.getDesc())
	}
	return ret, nil
}

func (self *SGuest) GetSecgroups() ([]SSecurityGroup, error) {
	secgrpQuery := SecurityGroupManager.Query()
	secgrpQuery.Filter(
		sqlchemy.OR(
			sqlchemy.Equals(secgrpQuery.Field("id"), self.SecgrpId),
			sqlchemy.In(secgrpQuery.Field("id"), GuestsecgroupManager.Query("secgroup_id").Equals("guest_id", self.Id).SubQuery()),
		),
	)
	secgroups := []SSecurityGroup{}
	err := db.FetchModelObjects(SecurityGroupManager, secgrpQuery, &secgroups)
	if err != nil {
		return nil, errors.Wrapf(err, "db.FetchModelObjects")
	}
	return secgroups, nil
}

func (self *SGuest) getAdminSecgroup() *SSecurityGroup {
	secGrp, _ := SecurityGroupManager.FetchSecgroupById(self.AdminSecgrpId)
	return secGrp
}

func (self *SGuest) getAdminSecgroupName() string {
	secgrp := self.getAdminSecgroup()
	if secgrp != nil {
		return secgrp.GetName()
	}
	return ""
}

// 获取多个安全组规则，优先级降序排序
func (self *SGuest) getSecurityGroupsRules() string {
	secgroups, _ := self.GetSecgroups()
	secgroupids := []string{}
	for _, secgroup := range secgroups {
		secgroupids = append(secgroupids, secgroup.Id)
	}
	q := SecurityGroupRuleManager.Query()
	q.Filter(sqlchemy.In(q.Field("secgroup_id"), secgroupids)).Desc(q.Field("priority"), q.Field("action"))
	secrules := []SSecurityGroupRule{}
	if err := db.FetchModelObjects(SecurityGroupRuleManager, q, &secrules); err != nil {
		log.Errorf("Get security group rules error: %v", err)
		return ""
	}
	rules := []string{}
	for _, rule := range secrules {
		rules = append(rules, rule.String())
	}
	return strings.Join(rules, SECURITY_GROUP_SEPARATOR)
}

func (self *SGuest) getNetworkSecurityGroupsRules(networkIndex int) string {
	gnss, _ := self.GetGuestNetworkSecgroups(networkIndex)
	secgroupids := []string{}
	for _, gns := range gnss {
		secgroupids = append(secgroupids, gns.SecgroupId)
	}
	q := SecurityGroupRuleManager.Query()
	q.Filter(sqlchemy.In(q.Field("secgroup_id"), secgroupids)).Desc(q.Field("priority"), q.Field("action"))
	secrules := []SSecurityGroupRule{}
	if err := db.FetchModelObjects(SecurityGroupRuleManager, q, &secrules); err != nil {
		log.Errorf("Get security group rules error: %v", err)
		return ""
	}
	rules := []string{}
	for _, rule := range secrules {
		rules = append(rules, rule.String())
	}
	return strings.Join(rules, SECURITY_GROUP_SEPARATOR)
}

func (self *SGuest) getAdminSecurityRules() string {
	secgrp := self.getAdminSecgroup()
	if secgrp != nil {
		ret, _ := secgrp.getSecurityRuleString()
		return ret
	}
	return ""
}

func (self *SGuest) IsFailureStatus() bool {
	return strings.Index(self.Status, "fail") >= 0
}

var (
	lostNamePattern = regexp.MustCompile(`-lost@\d{8}$`)
)

func (self *SGuest) GetIRegion(ctx context.Context) (cloudprovider.ICloudRegion, error) {
	host, err := self.GetHost()
	if err != nil {
		return nil, errors.Wrapf(err, "GetHost")
	}
	provider, err := host.GetDriver(ctx)
	if err != nil {
		return nil, errors.Wrapf(err, "host.GetDriver")
	}
	if provider.GetFactory().IsOnPremise() {
		return provider.GetOnPremiseIRegion()
	}
	return host.GetIRegion(ctx)
}

func (self *SGuest) SyncRemoveCloudVM(ctx context.Context, userCred mcclient.TokenCredential, check bool) error {
	lockman.LockObject(ctx, self)
	defer lockman.ReleaseObject(ctx, self)

	if self.BillingType == billing_api.BILLING_TYPE_PREPAID {
		diff, err := db.Update(self, func() error {
			self.BillingType = billing_api.BILLING_TYPE_POSTPAID
			self.ExpiredAt = time.Time{}
			return nil
		})
		if err != nil {
			return err
		}
		db.OpsLog.LogSyncUpdate(self, diff, userCred)
	}

	if self.IsFailureStatus() {
		return nil
	}

	iregion, err := self.GetIRegion(ctx)
	if err != nil {
		return err
	}

	if check {
		iVM, err := iregion.GetIVMById(self.ExternalId)
		if err == nil { //漂移归位
			if hostId := iVM.GetIHostId(); len(hostId) > 0 {
				host, err := db.FetchByExternalIdAndManagerId(HostManager, hostId, func(q *sqlchemy.SQuery) *sqlchemy.SQuery {
					host, _ := self.GetHost()
					if host != nil {
						return q.Equals("manager_id", host.ManagerId)
					}
					return q
				})
				if err != nil {
					log.Errorf("fetch vm %s(%s) host by id %s error: %v", self.Name, self.ExternalId, hostId, err)
					return nil
				}
				_, err = db.Update(self, func() error {
					self.HostId = host.GetId()
					self.Status = iVM.GetStatus()
					self.PowerStates = iVM.GetPowerStates()
					self.InferPowerStates()
					return nil
				})
				return err
			}
			// 公有云实例, 因为翻页查询导致实例返回结果漏查,且GetIHostId一般返回为空
			return nil
		} else if errors.Cause(err) != cloudprovider.ErrNotFound {
			return errors.Wrap(err, "GetIVMById")
		}
	}

	if self.Status != api.VM_UNKNOWN {
		self.SetStatus(ctx, userCred, api.VM_UNKNOWN, "Sync lost")
	}

	if options.Options.EnableSyncPurge {
		log.Debugf("purge removed resource %s", self.Name)
		err := self.purge(ctx, userCred)
		if err != nil {
			return err
		}
		notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{
			Obj:    self,
			Action: notifyclient.ActionSyncDelete,
		})

		return nil
	}

	if !lostNamePattern.MatchString(self.Name) {
		db.Update(self, func() error {
			self.Name = fmt.Sprintf("%s-lost@%s", self.Name, timeutils.ShortDate(time.Now()))
			return nil
		})
	}

	return nil
}

func (guest *SGuest) SyncAllWithCloudVM(ctx context.Context, userCred mcclient.TokenCredential, host *SHost, extVM cloudprovider.ICloudVM, syncStatus bool) error {
	if host == nil {
		return errors.Error("guest has no host")
	}

	provider := host.GetCloudprovider()
	if provider == nil {
		return errors.Error("host has no provider")
	}

	driver, err := provider.GetProvider(ctx)
	if err != nil {
		return errors.Wrap(err, "provider.GetProvider")
	}

	err = guest.syncWithCloudVM(ctx, userCred, driver, host, extVM, provider.GetOwnerId(), syncStatus)
	if err != nil {
		return errors.Wrap(err, "guest.syncWithCloudVM")
	}

	SyncVMPeripherals(ctx, userCred, guest, extVM, host, provider, driver)

	return nil
}

func (g *SGuest) SyncOsInfo(ctx context.Context, userCred mcclient.TokenCredential, extVM cloudprovider.IOSInfo) error {
	drv, err := g.GetDriver()
	if err != nil {
		return err
	}
	return drv.SyncOsInfo(ctx, userCred, g, extVM)
}

func (g *SGuest) SyncHostname(ext cloudprovider.ICloudVM) {
	hostname := pinyinutils.Text2Pinyin(ext.GetHostname())
	if len(hostname) > 128 {
		hostname = hostname[:128]
	}
	if len(hostname) > 0 {
		g.Hostname = hostname
	}
}

func (g *SGuest) syncWithCloudVM(ctx context.Context, userCred mcclient.TokenCredential, provider cloudprovider.ICloudProvider, host *SHost, extVM cloudprovider.ICloudVM, syncOwnerId mcclient.IIdentityProvider, syncStatus bool) error {
	recycle := false

	if provider.GetFactory().IsSupportPrepaidResources() && g.IsPrepaidRecycle() {
		recycle = true
	}

	diff, err := db.UpdateWithLock(ctx, g, func() error {
		if options.Options.EnableSyncName && !recycle {
			newName, _ := db.GenerateAlterName(g, extVM.GetName())
			if len(newName) > 0 && newName != g.Name {
				g.Name = newName
			}
		}

		g.SyncHostname(extVM)

		if !g.IsFailureStatus() && syncStatus {
			g.Status = extVM.GetStatus()
			if g.Status == api.VM_RUNNING || g.Status == api.VM_STARTING {
				g.HealthStatus = extVM.GetHealthStatus()
			}
			g.PowerStates = extVM.GetPowerStates()
			g.InferPowerStates()
		}

		g.VcpuCount = extVM.GetVcpuCount()
		g.CpuSockets = extVM.GetCpuSockets()
		g.BootOrder = extVM.GetBootOrder()
		g.Vga = extVM.GetVga()
		g.Vdi = extVM.GetVdi()
		if len(extVM.GetOsArch()) > 0 {
			g.OsArch = extVM.GetOsArch()
		}
		if len(g.OsType) == 0 {
			g.OsType = string(extVM.GetOsType())
		}
		if len(g.Bios) == 0 {
			g.Bios = string(extVM.GetBios())
		}
		g.Machine = extVM.GetMachine()
		if !recycle {
			g.HostId = host.Id
		}
		g.InternetMaxBandwidthOut = extVM.GetInternetMaxBandwidthOut()
		g.Throughput = extVM.GetThroughput()

		instanceType := extVM.GetInstanceType()

		if len(instanceType) > 0 {
			g.InstanceType = instanceType
		}

		memSizeMb := extVM.GetVmemSizeMB()
		if g.VmemSize == 0 || g.VmemSize != memSizeMb {
			if memSizeMb > 0 {
				g.VmemSize = memSizeMb
			} else {
				sku, _ := ServerSkuManager.FetchSkuByNameAndProvider(instanceType, provider.GetFactory().GetName(), false)
				if sku != nil && sku.MemorySizeMB > 0 {
					g.VmemSize = sku.MemorySizeMB
				}
			}
		}

		g.Hypervisor = extVM.GetHypervisor()

		if len(extVM.GetDescription()) > 0 {
			g.Description = extVM.GetDescription()
		}

		g.BillingType = billing_api.TBillingType(extVM.GetBillingType())
		g.ExpiredAt = time.Time{}
		g.AutoRenew = false
		if g.BillingType == billing_api.BILLING_TYPE_PREPAID {
			g.ExpiredAt = extVM.GetExpiredAt()
			g.AutoRenew = extVM.IsAutoRenew()
		}

		return nil
	})
	if err != nil {
		log.Errorf("%s", err)
		return err
	}

	db.OpsLog.LogSyncUpdate(g, diff, userCred)

	if len(diff) > 0 {
		notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{
			Obj:    g,
			Action: notifyclient.ActionSyncUpdate,
		})
	}

	g.SyncOsInfo(ctx, userCred, extVM)

	if account := host.GetCloudaccount(); account != nil {
		syncVirtualResourceMetadata(ctx, userCred, g, extVM, account.ReadOnly)
	}
	if cloudprovider := host.GetCloudprovider(); cloudprovider != nil {
		SyncCloudProject(ctx, userCred, g, syncOwnerId, extVM, cloudprovider)
	}

	if provider.GetFactory().IsSupportPrepaidResources() && recycle {
		vhost, _ := g.GetHost()
		err = vhost.syncWithCloudPrepaidVM(extVM, host)
		if err != nil {
			return err
		}
	}

	return nil
}

func (manager *SGuestManager) newCloudVM(ctx context.Context, userCred mcclient.TokenCredential, provider cloudprovider.ICloudProvider, host *SHost, extVM cloudprovider.ICloudVM, syncOwnerId mcclient.IIdentityProvider) (*SGuest, error) {

	guest := SGuest{}
	guest.SetModelManager(manager, &guest)

	guest.Status = extVM.GetStatus()
	guest.PowerStates = extVM.GetPowerStates()
	guest.InferPowerStates()
	guest.ExternalId = extVM.GetGlobalId()
	guest.VcpuCount = extVM.GetVcpuCount()
	guest.CpuSockets = extVM.GetCpuSockets()
	guest.BootOrder = extVM.GetBootOrder()
	guest.Vga = extVM.GetVga()
	guest.Vdi = extVM.GetVdi()
	guest.OsArch = extVM.GetOsArch()
	guest.OsType = string(extVM.GetOsType())
	guest.Bios = string(extVM.GetBios())
	guest.Machine = extVM.GetMachine()
	guest.Hypervisor = extVM.GetHypervisor()
	hostname := extVM.GetHostname()
	if len(hostname) == 0 {
		hostname = extVM.GetName()
	}
	guest.Hostname = pinyinutils.Text2Pinyin(hostname)
	guest.InternetMaxBandwidthOut = extVM.GetInternetMaxBandwidthOut()
	guest.Throughput = extVM.GetThroughput()
	guest.Description = extVM.GetDescription()

	guest.IsEmulated = extVM.IsEmulated()

	guest.BillingType = billing_api.TBillingType(extVM.GetBillingType())
	guest.ExpiredAt = time.Time{}
	guest.AutoRenew = false
	if guest.BillingType == billing_api.BILLING_TYPE_PREPAID {
		guest.ExpiredAt = extVM.GetExpiredAt()
		guest.AutoRenew = extVM.IsAutoRenew()
	}

	if createdAt := extVM.GetCreatedAt(); !createdAt.IsZero() {
		guest.CreatedAt = createdAt
	}

	guest.HostId = host.Id

	instanceType := extVM.GetInstanceType()

	if len(instanceType) > 0 {
		guest.InstanceType = instanceType
	}

	if extVM.GetHypervisor() == api.HYPERVISOR_AWS {
		sku, err := ServerSkuManager.FetchSkuByNameAndProvider(instanceType, api.CLOUD_PROVIDER_AWS, false)
		if err == nil {
			guest.VmemSize = sku.MemorySizeMB
		} else {
			guest.VmemSize = extVM.GetVmemSizeMB()
		}
	} else {
		guest.VmemSize = extVM.GetVmemSizeMB()
	}

	var err = func() error {
		lockman.LockRawObject(ctx, manager.Keyword(), "name")
		defer lockman.ReleaseRawObject(ctx, manager.Keyword(), "name")

		newName, err := db.GenerateName(ctx, manager, syncOwnerId, extVM.GetName())
		if err != nil {
			return errors.Wrapf(err, "db.GenerateName")
		}
		guest.Name = newName

		return manager.TableSpec().Insert(ctx, &guest)
	}()
	if err != nil {
		return nil, errors.Wrapf(err, "Insert")
	}

	guest.SyncOsInfo(ctx, userCred, extVM)

	syncVirtualResourceMetadata(ctx, userCred, &guest, extVM, false)

	if cloudprovider := host.GetCloudprovider(); cloudprovider != nil {
		SyncCloudProject(ctx, userCred, &guest, syncOwnerId, extVM, cloudprovider)
	}

	db.OpsLog.LogEvent(&guest, db.ACT_CREATE, guest.GetShortDesc(ctx), userCred)

	if guest.Status == api.VM_RUNNING {
		db.OpsLog.LogEvent(&guest, db.ACT_START, guest.GetShortDesc(ctx), userCred)
	}

	notifyclient.EventNotify(ctx, userCred, notifyclient.SEventNotifyParam{
		Obj:    &guest,
		Action: notifyclient.ActionSyncCreate,
	})

	drv, _ := guest.GetDriver()
	if drv != nil && drv.GetMaxSecurityGroupCount() == 0 {
		db.Update(&guest, func() error {
			guest.SecgrpId = ""
			return nil
		})
	}

	if guest.Status == api.VM_RUNNING {
		db.OpsLog.LogEvent(&guest, db.ACT_START, guest.GetShortDesc(ctx), userCred)
	}

	return &guest, nil
}

func (manager *SGuestManager) TotalCount(
	ctx context.Context,
	scope rbacscope.TRbacScope,
	ownerId mcclient.IIdentityProvider,
	rangeObjs []db.IStandaloneModel,
	status []string, hypervisors []string,
	includeSystem bool, pendingDelete bool,
	hostTypes []string, resourceTypes []string, providers []string, brands []string, cloudEnv string,
	since *time.Time,
	policyResult rbacutils.SPolicyResult,
) map[string]SGuestCountStat {
	return usageTotalGuestResourceCount(ctx, scope, ownerId, rangeObjs, status, hypervisors, includeSystem, pendingDelete, hostTypes, resourceTypes, providers, brands, cloudEnv, since, policyResult)
}

func (self *SGuest) detachNetworks(ctx context.Context, userCred mcclient.TokenCredential, gns []SGuestnetwork, reserve bool) error {
	err := GuestnetworkManager.DeleteGuestNics(ctx, userCred, gns, reserve)
	if err != nil {
		return err
	}
	host, _ := self.GetHost()
	if host != nil {
		host.ClearSchedDescCache() // ignore error
	}
	return nil
}

func (self *SGuest) getAttach2NetworkCount(net *SNetwork) (int, error) {
	q := GuestnetworkManager.Query()
	q = q.Equals("guest_id", self.Id).Equals("network_id", net.Id)
	return q.CountWithError()
}

func (self *SGuest) getUsableNicIndex() int {
	nics, err := self.GetNetworks("")
	if err != nil {
		return -1
	}
	maxIndex := len(nics)
	for i := 0; i <= maxIndex; i++ {
		found := true
		for j := range nics {
			if nics[j].Index == i {
				found = false
				break
			}
		}
		if found {
			return i
		}
	}
	panic(fmt.Sprintf("cannot find usable nic index for guest %s(%s)",
		self.Name, self.Id))
}

func (self *SGuest) setOSProfile(ctx context.Context, userCred mcclient.TokenCredential, profile jsonutils.JSONObject) error {
	return self.SetMetadata(ctx, "__os_profile__", profile, userCred)
}

func (self *SGuest) GetOSProfile() osprofile.SOSProfile {
	osName := self.GetOS()
	osProf := osprofile.GetOSProfile(osName, self.Hypervisor)
	val := self.GetMetadata(context.Background(), "__os_profile__", nil)
	if len(val) > 0 {
		jsonVal, _ := jsonutils.ParseString(val)
		if jsonVal != nil {
			jsonVal.Unmarshal(&osProf)
		}
	}
	return osProf
}

// Summary of network address allocation strategy
//
// # IpAddr when specified must be part of the network
//
// Use IpAddr without checking if it's already allocated when UseDesignatedIP
// is true.  See b31bc7fa ("feature: 1. baremetal server reuse host ip...")
//
// Try IpAddr from reserved pool when allowed by TryReserved.  Otherwise
// fallback to usual allocation method (AllocDir).  Error when
// RequireDesignatedIP is true and the allocated address does not match IpAddr
type Attach2NetworkArgs struct {
	Network *SNetwork

	IpAddr              string
	Ip6Addr             string
	AllocDir            api.IPAllocationDirection
	TryReserved         bool
	RequireDesignatedIP bool
	UseDesignatedIP     bool
	RequireIPv6         bool
	StrictIPv6          bool

	BwLimit        int
	NicDriver      string
	NumQueues      int
	RxTrafficLimit int64
	TxTrafficLimit int64
	NicConfs       []SNicConfig

	Virtual bool

	IsDefault    bool
	PortMappings api.GuestPortMappings

	BillingType billing_api.TBillingType
	ChargeType  billing_api.TNetChargeType

	PendingUsage quotas.IQuota
}

func (args *Attach2NetworkArgs) onceArgs(i int) attach2NetworkOnceArgs {
	if i < 0 || i > len(args.NicConfs)-1 {
		return attach2NetworkOnceArgs{}
	}
	r := attach2NetworkOnceArgs{
		network: args.Network,

		ipAddr:              args.IpAddr,
		ip6Addr:             args.Ip6Addr,
		allocDir:            args.AllocDir,
		tryReserved:         args.TryReserved,
		requireDesignatedIP: args.RequireDesignatedIP,
		useDesignatedIP:     args.UseDesignatedIP,
		requireIPv6:         args.RequireIPv6,
		strictIPv6:          args.StrictIPv6,

		bwLimit:        args.BwLimit,
		nicDriver:      args.NicDriver,
		numQueues:      args.NumQueues,
		txTrafficLimit: args.TxTrafficLimit,
		rxTrafficLimit: args.RxTrafficLimit,
		nicConf:        args.NicConfs[i],

		virtual: args.Virtual,

		isDefault: args.IsDefault,

		pendingUsage: args.PendingUsage,
		portMappings: args.PortMappings,

		billingType: args.BillingType,
		chargeType:  args.ChargeType,
	}
	if i > 0 {
		r.ipAddr = ""
		r.ip6Addr = ""
		r.bwLimit = 0
		r.virtual = true
		r.tryReserved = false
		r.requireDesignatedIP = false
		r.useDesignatedIP = false
		r.nicConf = args.NicConfs[i]
		r.nicDriver = ""
		r.numQueues = 1
		r.isDefault = false
		r.requireIPv6 = false
	}
	return r
}

type attach2NetworkOnceArgs struct {
	network *SNetwork

	ipAddr              string
	ip6Addr             string
	allocDir            api.IPAllocationDirection
	tryReserved         bool
	requireDesignatedIP bool
	useDesignatedIP     bool
	requireIPv6         bool
	strictIPv6          bool

	bwLimit        int
	nicDriver      string
	numQueues      int
	nicConf        SNicConfig
	teamWithMac    string
	rxTrafficLimit int64
	txTrafficLimit int64

	virtual bool

	isDefault bool

	pendingUsage quotas.IQuota
	portMappings api.GuestPortMappings

	billingType billing_api.TBillingType
	chargeType  billing_api.TNetChargeType
}

func (self *SGuest) Attach2Network(
	ctx context.Context,
	userCred mcclient.TokenCredential,
	args Attach2NetworkArgs,
) ([]SGuestnetwork, error) {
	log.Debugf("Attach2Network %s", jsonutils.Marshal(args))

	onceArgs := args.onceArgs(0)
	firstNic, err := self.attach2NetworkOnce(ctx, userCred, onceArgs)
	if err != nil {
		return nil, errors.Wrap(err, "self.attach2NetworkOnce")
	}
	retNics := []SGuestnetwork{*firstNic}
	if len(args.NicConfs) > 1 {
		firstMac, _ := netutils.ParseMac(firstNic.MacAddr)
		for i := 1; i < len(args.NicConfs); i += 1 {
			onceArgs := args.onceArgs(i)
			onceArgs.nicDriver = firstNic.Driver
			onceArgs.teamWithMac = firstNic.MacAddr
			if onceArgs.nicConf.Mac == "" {
				onceArgs.nicConf.Mac = firstMac.Add(i).String()
			}
			gn, err := self.attach2NetworkOnce(ctx, userCred, onceArgs)
			if err != nil {
				return retNics, errors.Wrap(err, "self.attach2NetworkOnce")
			}
			retNics = append(retNics, *gn)
		}
	}
	return retNics, nil
}

func (self *SGuest) attach2NetworkOnce(
	ctx context.Context,
	userCred mcclient.TokenCredential,
	args attach2NetworkOnceArgs,
) (*SGuestnetwork, error) {
	var (
		index     = args.nicConf.Index
		nicDriver = args.nicDriver
	)
	if index < 0 {
		index = self.getUsableNicIndex()
	}
	if nicDriver == "" {
		osProf := self.GetOSProfile()
		nicDriver = osProf.NetDriver
	}
	newArgs := newGuestNetworkArgs{
		guest:   self,
		network: args.network,

		index: index,

		ipAddr:              args.ipAddr,
		ip6Addr:             args.ip6Addr,
		allocDir:            args.allocDir,
		tryReserved:         args.tryReserved,
		requireDesignatedIP: args.requireDesignatedIP,
		useDesignatedIP:     args.useDesignatedIP,
		requireIPv6:         args.requireIPv6,
		strictIPv6:          args.strictIPv6,

		ifname:         args.nicConf.Ifname,
		macAddr:        args.nicConf.Mac,
		bwLimit:        args.bwLimit,
		nicDriver:      nicDriver,
		numQueues:      args.numQueues,
		teamWithMac:    args.teamWithMac,
		rxTrafficLimit: args.rxTrafficLimit,
		txTrafficLimit: args.txTrafficLimit,

		virtual: args.virtual,

		isDefault:    args.isDefault,
		portMappings: args.portMappings,

		billingType: args.billingType,
		chargeType:  args.chargeType,
	}
	lockman.LockClass(ctx, QuotaManager, self.ProjectId)
	defer lockman.ReleaseClass(ctx, QuotaManager, self.ProjectId)
	guestnic, err := GuestnetworkManager.newGuestNetwork(ctx, userCred, newArgs)
	if err != nil {
		return nil, errors.Wrap(err, "GuestnetworkManager.newGuestNetwork")
	}
	var (
		network      = args.network
		pendingUsage = args.pendingUsage
		teamWithMac  = args.teamWithMac
	)
	network.updateDnsRecord(guestnic, true)
	if pendingUsage != nil && len(teamWithMac) == 0 {
		cancelUsage := SRegionQuota{}
		if network.IsExitNetwork() {
			cancelUsage.Eport = 1
		} else {
			cancelUsage.Port = 1
		}
		keys, err := self.GetRegionalQuotaKeys()
		if err != nil {
			log.Warningf("self.GetRegionalQuotaKeys fail %s", err)
		}
		cancelUsage.SetKeys(keys)
		err = quotas.CancelPendingUsage(ctx, userCred, pendingUsage, &cancelUsage, true)
		if err != nil {
			log.Warningf("QuotaManager.CancelPendingUsage fail %s", err)
		}
	}
	db.OpsLog.LogAttachEvent(ctx, self, network, userCred, guestnic.GetShortDesc(ctx))
	return guestnic, nil
}

func getCloudNicNetwork(ctx context.Context, vnic cloudprovider.ICloudNic, host *SHost, ipList []string, index int) (*SNetwork, error) {
	vnetId := vnic.GetINetworkId()
	if len(vnetId) == 0 {
		if vnic.InClassicNetwork() {
			region, _ := host.GetRegion()
			cloudprovider := host.GetCloudprovider()
			vpc, err := VpcManager.GetOrCreateVpcForClassicNetwork(ctx, cloudprovider, region)
			if err != nil {
				return nil, errors.Wrap(err, "NewVpcForClassicNetwork")
			}
			zone, _ := host.GetZone()
			wire, err := WireManager.GetOrCreateWireForClassicNetwork(ctx, vpc, zone)
			if err != nil {
				return nil, errors.Wrap(err, "NewWireForClassicNetwork")
			}
			return NetworkManager.GetOrCreateClassicNetwork(ctx, wire)
		}
		ip := vnic.GetIP()
		if len(ip) == 0 {
			if index < len(ipList) {
				ip = ipList[index]
			}
			if len(ip) == 0 {
				return nil, fmt.Errorf("Cannot find inetwork for vnics %s: no ip", vnic.GetMAC())
			}
		}
		// find network by IP
		return host.getNetworkOfIPOnHost(ctx, ip)
	}
	localNetObj, err := db.FetchByExternalIdAndManagerId(NetworkManager, vnetId, func(q *sqlchemy.SQuery) *sqlchemy.SQuery {
		// vpc := VpcManager.Query().SubQuery()
		wire := WireManager.Query().SubQuery()
		return q.Join(wire, sqlchemy.Equals(q.Field("wire_id"), wire.Field("id"))).
			Filter(sqlchemy.Equals(wire.Field("manager_id"), host.ManagerId))
	})
	if err != nil {
		return nil, errors.Wrapf(err, "Cannot find network of external_id %s", vnetId)
	}
	localNet := localNetObj.(*SNetwork)
	return localNet, nil
}

func (self *SGuest) SyncVMNics(
	ctx context.Context,
	userCred mcclient.TokenCredential,
	host *SHost,
	vnics []cloudprovider.ICloudNic,
	ipList []string,
) compare.SyncResult {
	result := compare.SyncResult{}

	nics, err := self.GetNetworks("")
	if err != nil {
		result.Error(err)
		return result
	}

	removed := make([]SGuestnetwork, 0)
	commondb := make([]SGuestnetwork, 0)
	commonext := make([]cloudprovider.ICloudNic, 0)
	added := make([]cloudprovider.ICloudNic, 0)
	set := compare.SCompareSet{
		DBFunc:  "GetMAC",
		DBSet:   nics,
		ExtFunc: "GetMAC",
		ExtSet:  vnics,
	}
	err = compare.CompareSetsFunc(set, &removed, &commondb, &commonext, &added, nil)
	if err != nil {
		result.Error(errors.Wrapf(err, "compare.CompareSets"))
		return result
	}

	log.Debugf("SyncVMNics: removed: %d common: %d add: %d", len(removed), len(commondb), len(added))

	for i := 0; i < len(removed); i += 1 {
		err = self.detachNetworks(ctx, userCred, []SGuestnetwork{removed[i]}, false)
		if err != nil {
			result.DeleteError(err)
			continue
		}
		result.Delete()
	}

	for i := 0; i < len(commondb); i += 1 {
		err := NetworkAddressManager.syncGuestnetworkICloudNic(ctx, userCred, &commondb[i], commonext[i])
		if err != nil {
			result.UpdateError(err)
			continue
		}
		_, err = db.Update(&commondb[i], func() error {
			network, _ := commondb[i].GetNetwork()
			ip := commonext[i].GetIP()
			ip6 := commonext[i].GetIP6()
			if len(ip) > 0 {
				if !network.Contains(ip) {
					localNet, err := getCloudNicNetwork(ctx, commonext[i], host, ipList, i)
					if err != nil {
						return errors.Wrapf(err, "getCloudNicNetwork")
					}
					commondb[i].NetworkId = localNet.Id
					commondb[i].IpAddr = ip
				} else {
					commondb[i].IpAddr = ip
					commondb[i].Ip6Addr = ip6
				}
			}
			commondb[i].Driver = commonext[i].GetDriver()
			return nil
		})
		if err != nil {
			result.UpdateError(errors.Wrapf(err, "db.Update"))
			continue
		}
		result.Update()
	}

	syncIps := make([]string, 0)
	for i := 0; i < len(added); i += 1 {
		localNet, err := getCloudNicNetwork(ctx, added[i], host, ipList, i)
		if err != nil {
			log.Errorf("SyncVMNics getCloudNicNetwork add fail: %s", err)
			if ip := added[i].GetIP(); len(ip) > 0 {
				syncIps = append(syncIps, ip)
			}
			result.AddError(err)
			continue
		}

		nicConf := SNicConfig{
			Mac:    added[i].GetMAC(),
			Index:  -1,
			Ifname: "",
		}

		ip := added[i].GetIP()
		// vmware, may be sync fix ip
		if len(ip) == 0 && len(ipList) > 0 {
			ip = ipList[0]
		}

		// always try allocate from reserved pool
		guestnetworks, err := self.Attach2Network(ctx, userCred, Attach2NetworkArgs{
			Network:             localNet,
			IpAddr:              ip,
			Ip6Addr:             added[i].GetIP6(),
			NicDriver:           added[i].GetDriver(),
			TryReserved:         true,
			AllocDir:            api.IPAllocationDefault,
			RequireDesignatedIP: true,
			// UseDesignatedIP:     true,
			NicConfs: []SNicConfig{nicConf},
		})
		if err != nil {
			result.AddError(err)
			continue
		}
		if len(ipList) > 0 {
			// shift
			ipList = ipList[1:]
		}
		result.Add()
		for i := range guestnetworks {
			guestnetwork := &guestnetworks[i]
			if NetworkAddressManager.syncGuestnetworkICloudNic(
				ctx, userCred, guestnetwork, added[i]); err != nil {
				result.AddError(err)
			}
		}
	}

	if len(syncIps) > 0 {
		self.SetMetadata(ctx, "sync_ips", strings.Join(syncIps, ","), userCred)
	} else {
		self.SetMetadata(ctx, "sync_ips", "None", userCred)
	}

	return result
}

func (self *SGuest) IsAttach2Disk(disk *SDisk) (bool, error) {
	return self.isAttach2Disk(disk)
}

func (self *SGuest) isAttach2Disk(disk *SDisk) (bool, error) {
	q := GuestdiskManager.Query().Equals("disk_id", disk.Id).Equals("guest_id", self.Id)
	cnt, err := q.CountWithError()
	if err != nil {
		return false, err
	}
	return cnt > 0, nil
}

func (self *SGuest) getDiskIndex() int8 {
	guestdisks, _ := self.GetGuestDisks()
	var max uint
	for i := 0; i < len(guestdisks); i++ {
		if uint(guestdisks[i].Index) > max {
			max = uint(guestdisks[i].Index)
		}
	}

	idxs := make([]int, max+1)
	for i := 0; i < len(guestdisks); i++ {
		idxs[guestdisks[i].Index] = 1
	}

	// find first idx not set
	for i := 0; i < len(idxs); i++ {
		if idxs[i] != 1 {
			return int8(i)
		}
	}

	return int8(max + 1)
}

func (self *SGuest) AttachDisk(ctx context.Context, disk *SDisk, userCred mcclient.TokenCredential, driver string, cache string, mountpoint string, bootIndex *int8) error {
	return self.attach2Disk(ctx, disk, userCred, driver, cache, mountpoint, bootIndex)
}

func (self *SGuest) attach2Disk(ctx context.Context, disk *SDisk, userCred mcclient.TokenCredential, driver string, cache string, mountpoint string, bootIndex *int8) error {
	attached, err := self.isAttach2Disk(disk)
	if err != nil {
		return errors.Wrap(err, "isAttach2Disk")
	}
	if attached {
		return fmt.Errorf("Guest has been attached to disk")
	}

	if len(driver) == 0 {
		// depends the last disk of this guest
		existingDisks, _ := self.GetGuestDisks()
		if len(existingDisks) > 0 {
			prevDisk := existingDisks[len(existingDisks)-1]
			if prevDisk.Driver == api.DISK_DRIVER_IDE {
				driver = api.DISK_DRIVER_VIRTIO
			} else {
				driver = prevDisk.Driver
			}
		} else {
			osProf := self.GetOSProfile()
			driver = osProf.DiskDriver
		}
	}
	guestdisk := SGuestdisk{}
	guestdisk.SetModelManager(GuestdiskManager, &guestdisk)

	guestdisk.DiskId = disk.Id
	guestdisk.GuestId = self.Id

	lockman.LockObject(ctx, self)
	defer lockman.ReleaseObject(ctx, self)

	guestdisk.Index = self.getDiskIndex()
	if bootIndex != nil {
		guestdisk.BootIndex = *bootIndex
	} else {
		guestdisk.BootIndex = -1
	}
	err = guestdisk.DoSave(ctx, driver, cache, mountpoint)
	if err == nil {
		db.OpsLog.LogAttachEvent(ctx, self, disk, userCred, nil)
	}
	return err
}

func (self *SGuest) SyncVMDisks(
	ctx context.Context,
	userCred mcclient.TokenCredential,
	provider cloudprovider.ICloudProvider,
	host *SHost,
	vdisks []cloudprovider.ICloudDisk,
	syncOwnerId mcclient.IIdentityProvider,
) compare.SyncResult {
	lockman.LockRawObject(ctx, self.Id, DiskManager.Keyword())
	defer lockman.ReleaseRawObject(ctx, self.Id, DiskManager.Keyword())

	result := compare.SyncResult{}

	dbDisks, err := self.GetDisks()
	if err != nil {
		result.Error(errors.Wrapf(err, "GetDisks"))
		return result
	}

	removed := make([]SDisk, 0)
	commondb := make([]SDisk, 0)
	commonext := make([]cloudprovider.ICloudDisk, 0)
	added := make([]cloudprovider.ICloudDisk, 0)
	err = compare.CompareSets(dbDisks, vdisks, &removed, &commondb, &commonext, &added)
	if err != nil {
		result.Error(errors.Wrapf(err, "compare.CompareSets"))
		return result
	}

	for i := 0; i < len(removed); i += 1 {
		self.DetachDisk(ctx, &removed[i], userCred)
		result.Delete()
	}

	for i := 0; i < len(commondb); i += 1 {
		if commondb[i].PendingDeleted != self.PendingDeleted { //避免主机正常,磁盘在回收站的情况
			db.Update(&commondb[i], func() error {
				commondb[i].PendingDeleted = self.PendingDeleted
				return nil
			})
		}
		commondb[i].SyncCloudProjectId(userCred, self.GetOwnerId())
		result.Update()
	}

	for i := 0; i < len(added); i += 1 {
		disk, err := DiskManager.findOrCreateDisk(ctx, userCred, provider, added[i], -1, self.GetOwnerId(), host.ManagerId)
		if err != nil {
			result.AddError(errors.Wrapf(err, "findOrCreateDisk(%s)", added[i].GetGlobalId()))
			continue
		}
		disk.SyncCloudProjectId(userCred, self.GetOwnerId())
		err = self.attach2Disk(ctx, disk, userCred, added[i].GetDriver(), added[i].GetCacheMode(), added[i].GetMountpoint(), nil)
		if err != nil {
			result.AddError(err)
			continue
		}
		result.Add()
	}

	err = self.fixSysDiskIndex()
	if err != nil {
		result.Error(errors.Wrapf(err, "fixSysDiskIndex"))
	}

	return result
}

func (self *SGuest) setSystemDisk() error {
	sq := GuestdiskManager.Query("disk_id").Equals("guest_id", self.Id).Equals("index", 0).SubQuery()
	disks := DiskManager.Query().In("id", sq)
	disk := &SDisk{}
	disk.SetModelManager(DiskManager, disk)
	err := disks.First(disk)
	if err != nil {
		return err
	}
	_, err = db.Update(disk, func() error {
		disk.DiskType = api.DISK_TYPE_SYS
		return nil
	})
	return err
}

func (self *SGuest) fixSysDiskIndex() error {
	disks := DiskManager.Query().SubQuery()
	sysQ := GuestdiskManager.Query().Equals("guest_id", self.Id)
	sysQ = sysQ.Join(disks, sqlchemy.Equals(disks.Field("id"), sysQ.Field("disk_id"))).Filter(sqlchemy.Equals(disks.Field("disk_type"), api.DISK_TYPE_SYS))
	sysDisk := &SGuestdisk{}
	sysDisk.SetModelManager(GuestdiskManager, sysDisk)
	err := sysQ.First(sysDisk)
	if err != nil {
		if errors.Cause(err) == sql.ErrNoRows {
			return self.setSystemDisk()
		}
		return err
	}
	if sysDisk.Index == 0 {
		return nil
	}
	q := GuestdiskManager.Query().Equals("guest_id", self.Id).Equals("index", 0)

	firstDisk := &SGuestdisk{}
	firstDisk.SetModelManager(GuestdiskManager, firstDisk)
	err = q.First(firstDisk)
	if err != nil {
		return err
	}
	_, err = db.Update(firstDisk, func() error {
		firstDisk.Index = sysDisk.Index
		return nil
	})
	if err != nil {
		return err
	}
	_, err = db.Update(sysDisk, func() error {
		sysDisk.Index = 0
		return nil
	})
	return err
}

func filterGuestByRange(q *sqlchemy.SQuery, rangeObjs []db.IStandaloneModel, hostTypes []string, resourceTypes []string, providers []string, brands []string, cloudEnv string) *sqlchemy.SQuery {
	hosts := HostManager.Query().SubQuery()
	subq := hosts.Query(hosts.Field("id"))
	subq = AttachUsageQuery(subq, hosts, hostTypes, resourceTypes, providers, brands, cloudEnv, rangeObjs)

	q = q.In("host_id", subq.SubQuery())

	return q
}

type SGuestCountStat struct {
	TotalGuestCount       int
	TotalCpuCount         int
	TotalMemSize          int
	TotalDiskSize         int
	TotalIsolatedCount    int
	TotalBackupGuestCount int
	TotalBackupCpuCount   int
	TotalBackupMemSize    int
	TotalBackupDiskSize   int
}

func usageTotalGuestResourceCount(
	ctx context.Context,
	scope rbacscope.TRbacScope,
	ownerId mcclient.IIdentityProvider,
	rangeObjs []db.IStandaloneModel,
	status []string,
	hypervisors []string,
	includeSystem bool,
	pendingDelete bool,
	hostTypes []string,
	resourceTypes []string,
	providers []string, brands []string, cloudEnv string,
	since *time.Time,
	policyResult rbacutils.SPolicyResult,
) map[string]SGuestCountStat {
	countStat := make(map[string]SGuestCountStat)
	for _, arch := range []string{apis.OS_ARCH_ALL, apis.OS_ARCH_X86_64, apis.OS_ARCH_AARCH64} {
		allStat := usageTotalGuestResourceCountByArch(
			ctx, scope, ownerId, rangeObjs, status,
			hypervisors, includeSystem, pendingDelete,
			hostTypes, resourceTypes, providers, brands,
			cloudEnv, since, policyResult, arch,
		)
		countStat[arch] = allStat
	}
	return countStat
}

func usageTotalGuestResourceCountByArch(
	ctx context.Context,
	scope rbacscope.TRbacScope,
	ownerId mcclient.IIdentityProvider,
	rangeObjs []db.IStandaloneModel,
	status []string,
	hypervisors []string,
	includeSystem bool,
	pendingDelete bool,
	hostTypes []string,
	resourceTypes []string,
	providers []string, brands []string, cloudEnv string,
	since *time.Time,
	policyResult rbacutils.SPolicyResult,
	osArch string,
) SGuestCountStat {
	q, guests := _guestResourceCountQuery(
		ctx,
		scope, ownerId, rangeObjs, status, hypervisors,
		pendingDelete, hostTypes, resourceTypes, providers, brands, cloudEnv, since,
		policyResult, osArch,
	)
	if !includeSystem {
		q = q.Filter(sqlchemy.OR(
			sqlchemy.IsNull(guests.Field("is_system")), sqlchemy.IsFalse(guests.Field("is_system"))))
	}

	stat := SGuestCountStat{}
	row := q.Row()
	err := q.Row2Struct(row, &stat)
	if err != nil {
		log.Errorf("%s", err)
	}
	stat.TotalCpuCount += stat.TotalBackupCpuCount
	stat.TotalMemSize += stat.TotalBackupMemSize
	stat.TotalDiskSize += stat.TotalBackupDiskSize
	return stat
}

func _guestResourceCountQuery(
	ctx context.Context,
	scope rbacscope.TRbacScope,
	ownerId mcclient.IIdentityProvider,
	rangeObjs []db.IStandaloneModel,
	status []string,
	hypervisors []string,
	pendingDelete bool,
	hostTypes []string,
	resourceTypes []string,
	providers []string, brands []string, cloudEnv string,
	since *time.Time,
	policyResult rbacutils.SPolicyResult,
	osArch string,
) (*sqlchemy.SQuery, *sqlchemy.SSubQuery) {

	guestdisks := GuestdiskManager.Query().SubQuery()

	disks := DiskManager.Query().SubQuery()

	diskQuery := guestdisks.Query(guestdisks.Field("guest_id"), sqlchemy.SUM("guest_disk_size", disks.Field("disk_size")))
	diskQuery = diskQuery.Join(disks, sqlchemy.Equals(guestdisks.Field("disk_id"), disks.Field("id")))
	diskQuery = diskQuery.GroupBy(guestdisks.Field("guest_id"))
	diskSubQuery := diskQuery.SubQuery()

	backupDiskQuery := guestdisks.Query(guestdisks.Field("guest_id"), sqlchemy.SUM("guest_disk_size", disks.Field("disk_size")))
	backupDiskQuery = backupDiskQuery.LeftJoin(disks, sqlchemy.Equals(guestdisks.Field("disk_id"), disks.Field("id")))
	backupDiskQuery = backupDiskQuery.Filter(sqlchemy.IsNotEmpty(disks.Field("backup_storage_id")))
	backupDiskQuery = backupDiskQuery.GroupBy(guestdisks.Field("guest_id"))

	diskBackupSubQuery := backupDiskQuery.SubQuery()
	// diskBackupSubQuery := diskQuery.IsNotEmpty("backup_storage_id").SubQuery()

	isolated := IsolatedDeviceManager.Query().SubQuery()

	isoDevQuery := isolated.Query(isolated.Field("guest_id"), sqlchemy.COUNT("device_sum"))
	isoDevQuery = isoDevQuery.Filter(sqlchemy.IsNotNull(isolated.Field("guest_id")))
	isoDevQuery = isoDevQuery.GroupBy(isolated.Field("guest_id"))

	isoDevSubQuery := isoDevQuery.SubQuery()

	var gq *sqlchemy.SQuery
	if since != nil && !since.IsZero() {
		gq = GuestManager.RawQuery()
	} else {
		gq = GuestManager.Query()
	}
	if osArch != "" && osArch != apis.OS_ARCH_ALL {
		gq = gq.Equals("os_arch", osArch)
	}

	if len(rangeObjs) > 0 || len(hostTypes) > 0 || len(resourceTypes) > 0 || len(providers) > 0 || len(brands) > 0 || len(cloudEnv) > 0 {
		gq = filterGuestByRange(gq, rangeObjs, hostTypes, resourceTypes, providers, brands, cloudEnv)
	}

	switch scope {
	case rbacscope.ScopeSystem:
	case rbacscope.ScopeDomain:
		gq = gq.Filter(sqlchemy.Equals(gq.Field("domain_id"), ownerId.GetProjectDomainId()))
	case rbacscope.ScopeProject:
		gq = gq.Filter(sqlchemy.Equals(gq.Field("tenant_id"), ownerId.GetProjectId()))
	}

	if len(status) > 0 {
		gq = gq.Filter(sqlchemy.In(gq.Field("status"), status))
	}
	if len(hypervisors) > 0 {
		gq = gq.Filter(sqlchemy.In(gq.Field("hypervisor"), hypervisors))
	}

	if pendingDelete {
		gq = gq.Filter(sqlchemy.IsTrue(gq.Field("pending_deleted")))
	} else {
		gq = gq.Filter(sqlchemy.OR(sqlchemy.IsNull(gq.Field("pending_deleted")), sqlchemy.IsFalse(gq.Field("pending_deleted"))))
	}

	if since != nil && !since.IsZero() {
		gq = gq.Filter(sqlchemy.GT(gq.Field("created_at"), *since))
	}

	gq = db.ObjectIdQueryWithPolicyResult(ctx, gq, GuestManager, policyResult)

	guests := gq.SubQuery()

	guestBackupSubQuery := GuestManager.Query(
		"id",
		"vcpu_count",
		"vmem_size",
	).IsNotEmpty("backup_host_id").SubQuery()

	q := guests.Query(sqlchemy.COUNT("total_guest_count"),
		sqlchemy.SUM("total_cpu_count", guests.Field("vcpu_count")),
		sqlchemy.SUM("total_mem_size", guests.Field("vmem_size")),
		sqlchemy.SUM("total_disk_size", diskSubQuery.Field("guest_disk_size")),
		sqlchemy.SUM("total_isolated_count", isoDevSubQuery.Field("device_sum")),
		sqlchemy.SUM("total_backup_disk_size", diskBackupSubQuery.Field("guest_disk_size")),
		sqlchemy.SUM("total_backup_cpu_count", guestBackupSubQuery.Field("vcpu_count")),
		sqlchemy.SUM("total_backup_mem_size", guestBackupSubQuery.Field("vmem_size")),
		sqlchemy.COUNT("total_backup_guest_count", guestBackupSubQuery.Field("id")),
	)

	q = q.LeftJoin(guestBackupSubQuery, sqlchemy.Equals(guestBackupSubQuery.Field("id"), guests.Field("id")))

	q = q.LeftJoin(diskSubQuery, sqlchemy.Equals(diskSubQuery.Field("guest_id"), guests.Field("id")))
	q = q.LeftJoin(diskBackupSubQuery, sqlchemy.Equals(diskBackupSubQuery.Field("guest_id"), guests.Field("id")))

	q = q.LeftJoin(isoDevSubQuery, sqlchemy.Equals(isoDevSubQuery.Field("guest_id"), guests.Field("id")))

	return q, guests
}

func (self *SGuest) getDefaultNetworkConfig() *api.NetworkConfig {
	netConf := api.NetworkConfig{}
	netConf.BwLimit = options.Options.DefaultBandwidth
	osProf := self.GetOSProfile()
	netConf.Driver = osProf.NetDriver
	return &netConf
}

func (self *SGuest) CreateNetworksOnHost(
	ctx context.Context,
	userCred mcclient.TokenCredential,
	host *SHost,
	netArray []*api.NetworkConfig,
	pendingUsage, pendingUsageZone quotas.IQuota,
	candidateNets []*schedapi.CandidateNet,
) error {
	if len(netArray) == 0 {
		netConfig := self.getDefaultNetworkConfig()
		_, err := self.attach2RandomNetwork(ctx, userCred, host, netConfig, pendingUsage)
		return errors.Wrap(err, "self.attach2RandomNetwork")
	}
	for idx := range netArray {
		netConfig, err := parseNetworkInfo(ctx, userCred, netArray[idx])
		if err != nil {
			return errors.Wrapf(err, "parseNetworkInfo at %d", idx)
		}
		var candidateNet *schedapi.CandidateNet
		if len(candidateNets) > idx {
			candidateNet = candidateNets[idx]
		}
		networkIds := []string{}
		if candidateNet != nil {
			networkIds = candidateNet.NetworkIds
		}
		if idx == 0 && netConfig.NumQueues == 0 {
			numQueues := self.VcpuCount / 2
			if numQueues > 16 {
				numQueues = 16
			}
			netConfig.NumQueues = numQueues
		}
		gns, err := self.attach2NetworkDesc(ctx, userCred, host, netConfig, pendingUsage, networkIds)
		if err != nil {
			return errors.Wrap(err, "self.attach2NetworkDesc")
		}
		if netConfig.SriovDevice != nil {
			err = self.allocSriovNicDevice(ctx, userCred, host, &gns[0], netConfig, pendingUsageZone)
			if err != nil {
				return errors.Wrap(err, "self.allocSriovNicDevice")
			}
		}
		if len(netConfig.Secgroups) > 0 {
			err = self.SaveNetworkSecgroups(ctx, userCred, netConfig.Secgroups, gns[0].Index)
			if err != nil {
				return errors.Wrap(err, "SaveNetworkSecgroups")
			}
		}
	}
	return nil
}

func (self *SGuest) allocSriovNicDevice(
	ctx context.Context,
	userCred mcclient.TokenCredential,
	host *SHost,
	gn *SGuestnetwork, netConfig *api.NetworkConfig,
	pendingUsageZone quotas.IQuota,
) error {
	net, err := gn.GetNetwork()
	if err != nil {
		return errors.Wrapf(err, "GetNetwork")
	}
	netConfig.SriovDevice.NetworkIndex = &gn.Index
	netConfig.SriovDevice.WireId = net.WireId
	err = self.createIsolatedDeviceOnHost(ctx, userCred, host, netConfig.SriovDevice, pendingUsageZone, nil, nil)
	if err != nil {
		return errors.Wrap(err, "self.createIsolatedDeviceOnHost")
	}
	dev, err := self.GetIsolatedDeviceByNetworkIndex(gn.Index)
	if err != nil {
		return errors.Wrap(err, "self.GetIsolatedDeviceByNetworkIndex")
	}
	if dev.OvsOffloadInterface != "" {
		_, err = db.Update(gn, func() error {
			gn.Ifname = dev.OvsOffloadInterface
			return nil
		})
		if err != nil {
			return errors.Wrap(err, "update sriov network ifname")
		}
	}
	return nil
}

func (self *SGuest) attach2NetworkDesc(
	ctx context.Context,
	userCred mcclient.TokenCredential,
	host *SHost,
	netConfig *api.NetworkConfig,
	pendingUsage quotas.IQuota,
	candiateNetIds []string,
) ([]SGuestnetwork, error) {
	var gns []SGuestnetwork
	var errs []error

	tryNetworkIds := []string{}
	if len(netConfig.Network) > 0 {
		tryNetworkIds = append(tryNetworkIds, netConfig.Network)
	}
	if len(candiateNetIds) > 0 {
		// suggestion by scheduler
		tryNetworkIds = append(tryNetworkIds, candiateNetIds...)
	}

	if len(tryNetworkIds) > 0 {
		for _, tryNetwork := range tryNetworkIds {
			var err error
			netConfig.Network = tryNetwork
			gns, err = self.attach2NamedNetworkDesc(ctx, userCred, host, netConfig, pendingUsage)
			if err == nil {
				return gns, nil
			}
			errs = append(errs, err)
		}
		return nil, errors.NewAggregate(errs)
	} else {
		netConfig.Network = ""
		return self.attach2RandomNetwork(ctx, userCred, host, netConfig, pendingUsage)
	}
}

func (self *SGuest) attach2NamedNetworkDesc(ctx context.Context, userCred mcclient.TokenCredential, host *SHost, netConfig *api.NetworkConfig, pendingUsage quotas.IQuota) ([]SGuestnetwork, error) {
	driver, err := self.GetDriver()
	if err != nil {
		return nil, errors.Wrapf(err, "GetDriver")
	}
	net, nicConfs, allocDir, reuseAddr, err := driver.GetNamedNetworkConfiguration(self, ctx, userCred, host, netConfig)
	if err != nil {
		if errors.Cause(err) == sql.ErrNoRows {
			return nil, errors.Wrapf(httperrors.ErrResourceNotReady, "Network not avaiable on host %q", host.GetName())
		} else {
			return nil, errors.Wrapf(err, "GetNamedNetworkConfiguration on host %q", host.GetName())
		}
	}
	if net != nil {
		if len(nicConfs) == 0 {
			return nil, fmt.Errorf("no available network interface?")
		}
		var sriovWires []string
		if netConfig.SriovDevice != nil {
			if netConfig.SriovDevice.Id != "" {
				idev, err := IsolatedDeviceManager.FetchById(netConfig.SriovDevice.Id)
				if err != nil {
					return nil, errors.Wrap(err, "fetch isolated device")
				}
				dev, _ := idev.(*SIsolatedDevice)
				sriovWires = []string{dev.WireId}
			} else {
				wires, err := IsolatedDeviceManager.FindUnusedNicWiresByModel(netConfig.SriovDevice.Model)
				if err != nil {
					return nil, errors.Wrap(err, "FindUnusedNicWiresByModel")
				}
				sriovWires = wires
			}
			vpc, err := net.GetVpc()
			if err != nil {
				return nil, errors.Wrap(err, "attach2NamedNetworkDesc get vpc by network")
			}
			if vpc.Id == api.DEFAULT_VPC_ID && !utils.IsInStringArray(net.WireId, sriovWires) {
				return nil, fmt.Errorf("no available sriov nic for wire %s", net.WireId)
			}
		}

		gn, err := self.Attach2Network(ctx, userCred, Attach2NetworkArgs{
			Network:             net,
			PendingUsage:        pendingUsage,
			IpAddr:              netConfig.Address,
			Ip6Addr:             netConfig.Address6,
			RequireIPv6:         netConfig.RequireIPv6,
			StrictIPv6:          netConfig.StrictIPv6,
			NicDriver:           netConfig.Driver,
			NumQueues:           netConfig.NumQueues,
			BwLimit:             netConfig.BwLimit,
			RxTrafficLimit:      netConfig.RxTrafficLimit,
			TxTrafficLimit:      netConfig.TxTrafficLimit,
			Virtual:             netConfig.Vip,
			TryReserved:         netConfig.Reserved,
			AllocDir:            allocDir,
			RequireDesignatedIP: netConfig.RequireDesignatedIP,
			UseDesignatedIP:     reuseAddr,
			NicConfs:            nicConfs,

			IsDefault:    netConfig.IsDefault,
			PortMappings: netConfig.PortMappings,

			BillingType: netConfig.BillingType,
			ChargeType:  netConfig.ChargeType,
		})
		if err != nil {
			return nil, errors.Wrap(err, "Attach2Network fail")
		} else {
			return gn, nil
		}
	} else {
		return nil, fmt.Errorf("Network %s not available", netConfig.Network)
	}
}

func (self *SGuest) attach2RandomNetwork(ctx context.Context, userCred mcclient.TokenCredential, host *SHost, netConfig *api.NetworkConfig, pendingUsage quotas.IQuota) ([]SGuestnetwork, error) {
	driver, err := self.GetDriver()
	if err != nil {
		return nil, err
	}
	return driver.Attach2RandomNetwork(self, ctx, userCred, host, netConfig, pendingUsage)
}

func (self *SGuest) CreateDisksOnHost(
	ctx context.Context,
	userCred mcclient.TokenCredential,
	host *SHost,
	disks []*api.DiskConfig,
	pendingUsage quotas.IQuota,
	inheritBilling bool,
	isWithServerCreate bool,
	candidateDisks []*schedapi.CandidateDisk,
	backupCandidateDisks []*schedapi.CandidateDisk,
	autoAttach bool,
) error {
	for idx := 0; idx < len(disks); idx += 1 {
		if len(disks[idx].DiskId) > 0 && len(disks[idx].Storage) > 0 {
			continue
		}
		diskConfig, err := parseDiskInfo(ctx, userCred, disks[idx])
		if err != nil {
			return errors.Wrap(err, "parseDiskInfo")
		}
		var candidateDisk *schedapi.CandidateDisk
		var backupCandidateDisk *schedapi.CandidateDisk
		if len(candidateDisks) > idx {
			candidateDisk = candidateDisks[idx]
		}
		if len(backupCandidateDisks) != 0 && len(backupCandidateDisks) > idx {
			backupCandidateDisk = backupCandidateDisks[idx]
		}
		disk, err := self.createDiskOnHost(ctx, userCred, host, diskConfig, pendingUsage, inheritBilling, isWithServerCreate, candidateDisk, backupCandidateDisk, autoAttach)
		if err != nil {
			return err
		}
		diskConfig.DiskId = disk.Id
		disks[idx] = diskConfig
		if diskConfig.NVMEDevice != nil {
			err = self.attachNVMEDevice(ctx, userCred, host, pendingUsage, disk, diskConfig)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

func (self *SGuest) attachNVMEDevice(
	ctx context.Context, userCred mcclient.TokenCredential,
	host *SHost, pendingUsage quotas.IQuota,
	disk *SDisk, diskConfig *api.DiskConfig,
) error {
	gd := self.GetGuestDisk(disk.Id)
	diskConfig.NVMEDevice.DiskIndex = &gd.Index
	err := self.createIsolatedDeviceOnHost(ctx, userCred, host, diskConfig.NVMEDevice, pendingUsage, nil, nil)
	if err != nil {
		return errors.Wrap(err, "self.createIsolatedDeviceOnHost")
	}
	dev, err := self.GetIsolatedDeviceByDiskIndex(gd.Index)
	if err != nil {
		return errors.Wrap(err, "self.GetIsolatedDeviceByDiskIndex")
	}
	diskConfig.SizeMb = dev.NvmeSizeMB
	_, err = db.Update(disk, func() error {
		disk.DiskSize = dev.NvmeSizeMB
		return nil
	})
	if err != nil {
		return errors.Wrap(err, "update nvme disk size")
	}
	return nil
}

func (self *SGuest) CreateDiskOnStorage(ctx context.Context, userCred mcclient.TokenCredential, storage *SStorage,
	diskConfig *api.DiskConfig, pendingUsage quotas.IQuota, inheritBilling bool, isWithServerCreate bool) (*SDisk, error) {
	lockman.LockObject(ctx, storage)
	defer lockman.ReleaseObject(ctx, storage)

	lockman.LockClass(ctx, QuotaManager, self.ProjectId)
	defer lockman.ReleaseClass(ctx, QuotaManager, self.ProjectId)

	diskName := fmt.Sprintf("vdisk-%s-%d", pinyinutils.Text2Pinyin(self.Name), time.Now().UnixNano())

	billingType := billing_api.BILLING_TYPE_POSTPAID
	billingCycle := ""
	if inheritBilling {
		billingType = self.BillingType
		billingCycle = self.BillingCycle
	}

	autoDelete := false
	if storage.IsLocal() || billingType == billing_api.BILLING_TYPE_PREPAID || isWithServerCreate {
		autoDelete = true
	}
	if diskConfig.AutoDelete != nil {
		autoDelete = *diskConfig.AutoDelete
	}
	disk, err := storage.createDisk(ctx, diskName, diskConfig, userCred, self.GetOwnerId(), autoDelete, self.IsSystem,
		billingType, billingCycle, self.EncryptKeyId)

	if err != nil {
		return nil, err
	}

	if isWithServerCreate {
		meta, _ := self.GetAllUserMetadata()
		if len(meta) > 0 {
			disk.SetUserMetadataAll(ctx, meta, userCred)
		}
	}

	if pendingUsage != nil {
		cancelUsage := SQuota{}
		cancelUsage.Storage = disk.DiskSize
		keys, err := self.GetQuotaKeys()
		if err != nil {
			return nil, err
		}
		cancelUsage.SetKeys(keys)
		err = quotas.CancelPendingUsage(ctx, userCred, pendingUsage, &cancelUsage, true)
		if err != nil {
			return nil, err
		}
	}

	return disk, nil
}

func (self *SGuest) ChooseHostStorage(host *SHost, diskConfig *api.DiskConfig, candidate *schedapi.CandidateDisk) (*SStorage, error) {
	drv, err := self.GetDriver()
	if err != nil {
		return nil, err
	}

	if candidate == nil || len(candidate.StorageIds) == 0 {
		return drv.ChooseHostStorage(host, self, diskConfig, nil)
	}
	return drv.ChooseHostStorage(host, self, diskConfig, candidate.StorageIds)
}

func (self *SGuest) createDiskOnHost(
	ctx context.Context,
	userCred mcclient.TokenCredential,
	host *SHost,
	diskConfig *api.DiskConfig,
	pendingUsage quotas.IQuota,
	inheritBilling bool,
	isWithServerCreate bool,
	candidate *schedapi.CandidateDisk,
	backupCandidate *schedapi.CandidateDisk,
	autoAttach bool,
) (*SDisk, error) {
	var (
		storage *SStorage
		err     error
	)
	if len(diskConfig.Storage) > 0 {
		_storage, err := StorageManager.FetchByIdOrName(ctx, userCred, diskConfig.Storage)
		if err != nil {
			if err == sql.ErrNoRows {
				return nil, httperrors.NewResourceNotFoundError2("storage", diskConfig.Storage)
			}
			return nil, fmt.Errorf("get storage(%s) error: %v", diskConfig.Storage, err)
		}
		storage = _storage.(*SStorage)
	} else {
		storage, err = self.ChooseHostStorage(host, diskConfig, candidate)
		if err != nil {
			return nil, errors.Wrap(err, "ChooseHostStorage")
		}
	}
	if storage == nil {
		return nil, fmt.Errorf("No storage on %s to create disk for %s", host.GetName(), diskConfig.Backend)
	}
	log.Debugf("Choose storage %s:%s for disk %#v", storage.Name, storage.Id, diskConfig)
	disk, err := self.CreateDiskOnStorage(ctx, userCred, storage, diskConfig, pendingUsage, inheritBilling, isWithServerCreate)
	if err != nil {
		return nil, err
	}
	if diskConfig.ExistingPath != "" {
		disk.SetMetadata(ctx, api.DISK_META_EXISTING_PATH, diskConfig.ExistingPath, userCred)
	}

	if len(self.BackupHostId) > 0 {
		backupHost := HostManager.FetchHostById(self.BackupHostId)
		backupStorage, err := self.ChooseHostStorage(backupHost, diskConfig, backupCandidate)
		if err != nil {
			return nil, errors.Wrap(err, "ChooseHostStorage")
		}
		diff, err := db.Update(disk, func() error {
			disk.BackupStorageId = backupStorage.Id
			return nil
		})
		if err != nil {
			log.Errorf("Disk save backup storage error")
			return disk, err
		}
		db.OpsLog.LogEvent(disk, db.ACT_UPDATE, diff, userCred)
	}
	if autoAttach {
		err = self.attach2Disk(ctx, disk, userCred, diskConfig.Driver, diskConfig.Cache, diskConfig.Mountpoint, diskConfig.BootIndex)
	}
	err = self.InheritTo(ctx, userCred, disk)
	if err != nil {
		return nil, errors.Wrapf(err, "unable to inherit from guest %s to disk %s", self.GetId(), disk.GetId())
	}
	return disk, err
}

func (self *SGuest) CreateIsolatedDeviceOnHost(ctx context.Context, userCred mcclient.TokenCredential, host *SHost, devs []*api.IsolatedDeviceConfig, pendingUsage quotas.IQuota) error {
	var numaNodes []int
	if self.IsSchedulerNumaAllocate() {
		numaNodes = make([]int, 0)
		cpuNumaPin := make([]schedapi.SCpuNumaPin, 0)
		self.CpuNumaPin.Unmarshal(&cpuNumaPin)

		for i := range cpuNumaPin {
			numaNodes = append(numaNodes, cpuNumaPin[i].NodeId)
		}
	}

	lockman.LockObject(ctx, host)
	defer lockman.ReleaseObject(ctx, host)
	usedDeviceMap := map[string]*SIsolatedDevice{}
	for _, devConfig := range devs {
		if devConfig.DevType == api.NIC_TYPE || devConfig.DevType == api.NVME_PT_TYPE {
			continue
		}
		err := self.createIsolatedDeviceOnHost(ctx, userCred, host, devConfig, pendingUsage, usedDeviceMap, numaNodes)
		if err != nil {
			return err
		}
	}
	return nil
}

func (self *SGuest) createIsolatedDeviceOnHost(ctx context.Context, userCred mcclient.TokenCredential, host *SHost, devConfig *api.IsolatedDeviceConfig, pendingUsage quotas.IQuota, usedDevMap map[string]*SIsolatedDevice, preferNumaNodes []int) error {
	lockman.LockClass(ctx, QuotaManager, self.ProjectId)
	defer lockman.ReleaseClass(ctx, QuotaManager, self.ProjectId)

	err := IsolatedDeviceManager.attachHostDeviceToGuestByDesc(ctx, self, host, devConfig, userCred, usedDevMap, preferNumaNodes)
	if err != nil {
		return err
	}

	cancelUsage := SQuota{IsolatedDevice: 1}
	keys, err := self.GetQuotaKeys()
	if err != nil {
		return err
	}
	cancelUsage.SetKeys(keys)
	err = quotas.CancelPendingUsage(ctx, userCred, pendingUsage, &cancelUsage, true) // success
	return err
}

func (self *SGuest) JoinGroups(ctx context.Context, userCred mcclient.TokenCredential, groupIds []string) error {
	for _, id := range groupIds {
		_, err := GroupguestManager.Attach(ctx, id, self.Id)
		if err != nil {
			return err
		}
	}
	return nil
}

type SGuestDiskCategory struct {
	Root *SDisk
	Swap []*SDisk
	Data []*SDisk
}

func (self *SGuest) CategorizeDisks() SGuestDiskCategory {
	diskCat := SGuestDiskCategory{}
	guestdisks, err := self.GetGuestDisks()
	if err != nil {
		log.Errorf("no disk for this server!!!")
		return diskCat
	}
	for _, gd := range guestdisks {
		if diskCat.Root == nil {
			diskCat.Root = gd.GetDisk()
		} else {
			disk := gd.GetDisk()
			if disk.FsFormat == "swap" {
				diskCat.Swap = append(diskCat.Swap, disk)
			} else {
				diskCat.Data = append(diskCat.Data, disk)
			}
		}
	}
	return diskCat
}

type SGuestNicCategory struct {
	InternalNics []SGuestnetwork
	ExternalNics []SGuestnetwork
}

func (self *SGuest) CategorizeNics() SGuestNicCategory {
	netCat := SGuestNicCategory{}

	guestnics, err := self.GetNetworks("")
	if err != nil {
		log.Errorf("no nics for this server!!! %s", err)
		return netCat
	}

	for _, gn := range guestnics {
		if gn.IsExit(nil) {
			netCat.ExternalNics = append(netCat.ExternalNics, gn)
		} else {
			netCat.InternalNics = append(netCat.InternalNics, gn)
		}
	}
	return netCat
}

func (self *SGuest) LeaveAllGroups(ctx context.Context, userCred mcclient.TokenCredential) {
	groupGuests := make([]SGroupguest, 0)
	q := GroupguestManager.Query()
	err := q.Filter(sqlchemy.Equals(q.Field("guest_id"), self.Id)).All(&groupGuests)
	if err != nil {
		log.Errorf("query by guest_id %s: %v", self.Id, err)
		return
	}
	for _, gg := range groupGuests {
		gg.SetModelManager(GroupguestManager, &gg)
		gg.Delete(context.Background(), userCred)
		var group SGroup
		gq := GroupManager.Query()
		err := gq.Filter(sqlchemy.Equals(gq.Field("id"), gg.GroupId)).First(&group)
		if err != nil {
			log.Errorf("get by group id %s: %v", gg.GroupId, err)
			return
		}
		group.SetModelManager(GroupManager, &group)
		db.OpsLog.LogDetachEvent(ctx, self, &group, userCred, nil)
	}
}

func (self *SGuest) DetachAllNetworks(ctx context.Context, userCred mcclient.TokenCredential) error {
	// from clouds.models.portmaps import Portmaps
	// Portmaps.delete_guest_network_portmaps(self, user_cred)
	gns, err := self.GetNetworks("")
	if err != nil {
		return err
	}
	return GuestnetworkManager.DeleteGuestNics(ctx, userCred, gns, false)
}

func (self *SGuest) EjectIso(cdromOrdinal int64, userCred mcclient.TokenCredential) bool {
	cdrom := self.getCdrom(false, cdromOrdinal)
	if cdrom != nil && len(cdrom.ImageId) > 0 {
		imageId := cdrom.ImageId
		if cdrom.ejectIso() {
			db.OpsLog.LogEvent(self, db.ACT_ISO_DETACH, imageId, userCred)
			return true
		}
	}
	return false
}

func (self *SGuest) EjectAllIso(userCred mcclient.TokenCredential) bool {
	cdroms, _ := self.getCdroms()
	for _, cdrom := range cdroms {
		if len(cdrom.ImageId) > 0 {
			imageId := cdrom.ImageId
			if cdrom.ejectIso() {
				db.OpsLog.LogEvent(self, db.ACT_ISO_DETACH, imageId, userCred)
			} else {
				return false
			}
		}
	}
	return true
}

func (self *SGuest) EjectVfd(floppyOrdinal int64, userCred mcclient.TokenCredential) bool {
	floppy := self.getFloppy(false, floppyOrdinal)
	if floppy != nil && len(floppy.ImageId) > 0 {
		imageId := floppy.ImageId
		if floppy.ejectVfd() {
			db.OpsLog.LogEvent(self, db.ACT_VFD_DETACH, imageId, userCred)
			return true
		}
	}
	return false
}

func (self *SGuest) EjectAllVfd(userCred mcclient.TokenCredential) bool {
	floppys, _ := self.getFloppys()
	for _, floppy := range floppys {
		if len(floppy.ImageId) > 0 {
			imageId := floppy.ImageId
			if floppy.ejectVfd() {
				db.OpsLog.LogEvent(self, db.ACT_ISO_DETACH, imageId, userCred)
			} else {
				return false
			}
		}
	}
	return true
}

func (self *SGuest) Delete(ctx context.Context, userCred mcclient.TokenCredential) error {
	// self.SVirtualResourceBase.Delete(ctx, userCred)
	// override
	log.Infof("guest delete do nothing")
	return nil
}

func (self *SGuest) CleanTapRecords(ctx context.Context, userCred mcclient.TokenCredential) error {
	// delete tap devices
	if err := NetTapServiceManager.removeTapServicesByGuestId(ctx, userCred, self.Id); err != nil {
		return errors.Wrap(err, "NetTapServiceManager.getTapServicesByGuestId")
	}
	if err := NetTapFlowManager.removeTapFlowsByGuestId(ctx, userCred, self.Id); err != nil {
		return errors.Wrap(err, "NetTapFlowManager.getTapServicesByGuestId")
	}
	return nil
}

func (self *SGuest) GetLoadbalancerBackends() ([]SLoadbalancerBackend, error) {
	q := LoadbalancerBackendManager.Query().Equals("backend_id", self.Id)
	ret := []SLoadbalancerBackend{}
	return ret, db.FetchModelObjects(LoadbalancerBackendManager, q, &ret)
}

func (self *SGuest) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error {
	SnapshotPolicyResourceManager.RemoveByResource(self.Id, api.SNAPSHOT_POLICY_TYPE_SERVER)
	return self.purge(ctx, userCred)
}

func (self *SGuest) AllowDeleteItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
	overridePendingDelete := false
	purge := false
	if query != nil {
		overridePendingDelete = jsonutils.QueryBoolean(query, "override_pending_delete", false)
		purge = jsonutils.QueryBoolean(query, "purge", false)
	}
	if (overridePendingDelete || purge) && !db.IsAdminAllowDelete(ctx, userCred, self) {
		return false
	}
	return self.IsOwner(userCred) || db.IsAdminAllowDelete(ctx, userCred, self)
}

// 删除虚拟机
func (self *SGuest) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query api.ServerDeleteInput, data jsonutils.JSONObject) error {
	return self.StartDeleteGuestTask(ctx, userCred, "", query)
}

func (self *SGuest) DeleteAllDisksInDB(ctx context.Context, userCred mcclient.TokenCredential) error {
	guestDisks, err := self.GetGuestDisks()
	if err != nil {
		return errors.Wrapf(err, "GetGuestDisks")
	}
	for _, guestdisk := range guestDisks {
		disk := guestdisk.GetDisk()
		err := guestdisk.Detach(ctx, userCred)
		if err != nil {
			return errors.Wrapf(err, "guestdisk.Detach guest_id: %s disk_id: %s", guestdisk.GuestId, guestdisk.DiskId)
		}

		if disk != nil {
			cnt, err := disk.GetGuestDiskCount()
			if err != nil {
				return errors.Wrap(err, "disk.GetGuestDiskCount")
			}
			if cnt == 0 {
				db.OpsLog.LogEvent(disk, db.ACT_DELETE, nil, userCred)
				db.OpsLog.LogEvent(disk, db.ACT_DELOCATE, nil, userCred)
				err = disk.RealDelete(ctx, userCred)
				if err != nil {
					return errors.Wrap(err, "disk.RealDelete")
				}
			}
		}
	}
	return nil
}

func (self *SGuest) DeleteAllInstanceSnapshotInDB(ctx context.Context, userCred mcclient.TokenCredential) error {
	isps, err := self.GetInstanceSnapshots()
	if err != nil {
		return errors.Wrap(err, "unable to GetInstanceSnapshots")
	}
	for i := range isps {
		err = isps[i].RealDelete(ctx, userCred)
		return errors.Wrapf(err, "unable to real delete instance snapshot %q for guest %q", isps[i].GetName(), self.GetId())
	}
	return nil
}

func (self *SGuest) isNeedDoResetPasswd() bool {
	guestdisks, _ := self.GetGuestDisks()
	if len(guestdisks) > 0 {
		disk := guestdisks[0].GetDisk()
		if len(disk.SnapshotId) > 0 {
			return false
		}
	}
	return true
}

func (self *SGuest) IsSchedulerNumaAllocate() bool {
	cpuNumaPinType := self.GetMetadata(context.Background(), api.VM_METADATA_CPU_NUMA_PIN_TYPE, nil)
	return cpuNumaPinType == api.VM_CPU_NUMA_PIN_SCHEDULER && self.CpuNumaPin != nil
}

func (self *SGuest) GetDeployConfigOnHost(ctx context.Context, userCred mcclient.TokenCredential, host *SHost, params *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
	config := jsonutils.NewDict()

	drv, err := self.GetDriver()
	if err != nil {
		return nil, err
	}

	desc, err := drv.GetJsonDescAtHost(ctx, userCred, self, host, params)
	if err != nil {
		return nil, errors.Wrapf(err, "GetJsonDescAtHost")
	}
	config.Add(desc, "desc")

	deploys, err := cmdline.FetchDeployConfigsByJSON(params)
	if err != nil {
		return nil, err
	}

	if len(deploys) > 0 {
		config.Add(jsonutils.Marshal(deploys), "deploys")
	}

	deployAction, _ := params.GetString("deploy_action")
	if len(deployAction) == 0 {
		deployAction = "deploy"
	}

	config.Add(jsonutils.NewBool(jsonutils.QueryBoolean(params, "enable_cloud_init", false)), "enable_cloud_init")

	if account, _ := params.GetString("login_account"); len(account) > 0 {
		config.Set("login_account", jsonutils.NewString(account))
	}

	resetPasswd := jsonutils.QueryBoolean(params, "reset_password", true)
	if deployAction == "create" && resetPasswd {
		resetPasswd = self.isNeedDoResetPasswd()
	}

	if resetPasswd {
		config.Add(jsonutils.JSONTrue, "reset_password")
		passwd, _ := params.GetString("password")
		if len(passwd) > 0 {
			config.Add(jsonutils.NewString(passwd), "password")
		}
		keypair := self.getKeypair()
		if keypair != nil {
			config.Add(jsonutils.NewString(keypair.PublicKey), "public_key")
			config.Add(jsonutils.NewString(keypair.Name), "keypair_name")
		}
		deletePubKey, _ := params.GetString("delete_public_key")
		if len(deletePubKey) > 0 {
			config.Add(jsonutils.NewString(deletePubKey), "delete_public_key")
		}
	} else {
		config.Add(jsonutils.JSONFalse, "reset_password")
	}

	// add default public keys
	_, adminPubKey, err := sshkeys.GetSshAdminKeypair(ctx)
	if err != nil {
		log.Errorf("fail to get ssh admin public key %s", err)
	}

	_, projPubKey, err := sshkeys.GetSshProjectKeypair(ctx, self.ProjectId)

	if err != nil {
		log.Errorf("fail to get ssh project public key %s", err)
	}

	config.Add(jsonutils.NewString(adminPubKey), "admin_public_key")
	config.Add(jsonutils.NewString(projPubKey), "project_public_key")

	config.Add(jsonutils.NewString(deployAction), "action")

	onFinish := "shutdown"
	if jsonutils.QueryBoolean(params, "auto_start", false) || jsonutils.QueryBoolean(params, "restart", false) {
		onFinish = "none"
	} else if utils.IsInStringArray(self.Status, []string{api.VM_ADMIN}) {
		onFinish = "none"
	}

	config.Add(jsonutils.NewString(onFinish), "on_finish")

	if jsonutils.QueryBoolean(params, "deploy_telegraf", false) {
		influxdbUrl := drv.FetchMonitorUrl(ctx, self)
		config.Add(jsonutils.JSONTrue, "deploy_telegraf")
		serverDetails, err := self.getDetails(ctx, userCred)
		if err != nil {
			return nil, errors.Wrap(err, "get details")
		}
		telegrafConf, err := devtool_utils.GenerateTelegrafConf(
			serverDetails, influxdbUrl, self.OsType, self.Hypervisor)
		if err != nil {
			return nil, errors.Wrap(err, "get telegraf conf")
		}
		config.Add(jsonutils.NewString(telegrafConf), "telegraf_conf")
	}

	return config, nil
}

func (self *SGuest) getDetails(ctx context.Context, userCred mcclient.TokenCredential) (*api.ServerDetails, error) {
	res := GuestManager.FetchCustomizeColumns(ctx, userCred, jsonutils.NewDict(), []interface{}{self}, nil, false)
	jsonDict := jsonutils.Marshal(res[0]).(*jsonutils.JSONDict)
	jsonDict.Update(jsonutils.Marshal(self).(*jsonutils.JSONDict))
	serverDetails := new(api.ServerDetails)
	err := jsonDict.Unmarshal(serverDetails)
	if err != nil {
		return nil, err
	}
	return serverDetails, nil
}

func (self *SGuest) isBootIndexDuplicated(bootIndex int8) (bool, error) {
	if bootIndex < 0 {
		return false, nil
	}
	cdroms, err := self.getCdroms()
	if err != nil {
		return true, err
	}
	for i := 0; i < len(cdroms); i++ {
		if cdroms[i].BootIndex == bootIndex {
			return true, nil
		}
	}
	gd, err := self.GetGuestDisks()
	if err != nil {
		return true, err
	}
	for i := 0; i < len(gd); i++ {
		if gd[i].BootIndex == bootIndex {
			return true, nil
		}
	}
	return false, nil
}

func (self *SGuest) getVga() string {
	if utils.IsInStringArray(self.Vga, []string{"cirrus", "vmware", "qxl", "virtio", "std"}) {
		return self.Vga
	}
	return "std"
}

func (self *SGuest) GetVdi() string {
	if utils.IsInStringArray(self.Vdi, []string{"vnc", "spice"}) {
		return self.Vdi
	}
	return "vnc"
}

func (self *SGuest) getMachine() string {
	if utils.IsInStringArray(self.Machine, []string{"pc", "q35"}) {
		return self.Machine
	}
	return "pc"
}

func (self *SGuest) getBios() string {
	if utils.IsInStringArray(self.Bios, []string{"BIOS", "UEFI"}) {
		return self.Bios
	}
	return "BIOS"
}

func (self *SGuest) getKvmOptions() string {
	return self.GetMetadata(context.Background(), "kvm", nil)
}

func (self *SGuest) getExtraOptions(ctx context.Context) jsonutils.JSONObject {
	return self.GetMetadataJson(ctx, "extra_options", nil)
}

func (self *SGuest) GetIsolatedDevices() ([]SIsolatedDevice, error) {
	q := IsolatedDeviceManager.Query().Equals("guest_id", self.Id)
	devs := []SIsolatedDevice{}
	err := db.FetchModelObjects(IsolatedDeviceManager, q, &devs)
	if err != nil {
		return nil, errors.Wrapf(err, "db.FetchModelObjects")
	}
	return devs, nil
}

func (self *SGuest) GetIsolatedDeviceByNetworkIndex(index int) (*SIsolatedDevice, error) {
	dev := SIsolatedDevice{}
	q := IsolatedDeviceManager.Query().Equals("guest_id", self.Id).Equals("network_index", index)
	if cnt, err := q.CountWithError(); err != nil {
		return nil, err
	} else if cnt == 0 {
		return nil, nil
	}
	err := q.First(&dev)
	if err != nil {
		return nil, err
	}
	dev.SetModelManager(IsolatedDeviceManager, &dev)
	return &dev, nil
}

func (self *SGuest) GetIsolatedDeviceByDiskIndex(index int8) (*SIsolatedDevice, error) {
	dev := SIsolatedDevice{}
	q := IsolatedDeviceManager.Query().Equals("guest_id", self.Id).Equals("disk_index", index)
	if cnt, err := q.CountWithError(); err != nil {
		return nil, err
	} else if cnt == 0 {
		return nil, nil
	}
	err := q.First(&dev)
	if err != nil {
		return nil, err
	}
	dev.SetModelManager(IsolatedDeviceManager, &dev)
	return &dev, nil
}

func (self *SGuest) GetJsonDescAtHypervisor(ctx context.Context, host *SHost) *api.GuestJsonDesc {
	desc := &api.GuestJsonDesc{
		Name:         self.Name,
		Hostname:     self.Hostname,
		Description:  self.Description,
		UUID:         self.Id,
		Mem:          self.VmemSize,
		Cpu:          self.VcpuCount,
		CpuSockets:   self.CpuSockets,
		Vga:          self.getVga(),
		Vdi:          self.GetVdi(),
		Machine:      self.getMachine(),
		Bios:         self.getBios(),
		BootOrder:    self.BootOrder,
		SrcIpCheck:   self.SrcIpCheck.Bool(),
		SrcMacCheck:  self.SrcMacCheck.Bool(),
		HostId:       host.Id,
		HostAccessIp: host.AccessIp,
		HostEIP:      host.PublicIp,

		EncryptKeyId: self.EncryptKeyId,

		IsDaemon: self.IsDaemon.Bool(),

		LightMode:  self.RescueMode,
		Hypervisor: self.GetHypervisor(),

		EnableEsxiSwap: options.Options.EnableEsxiSwap,
	}

	if len(self.BackupHostId) > 0 {
		if self.HostId == host.Id {
			isMaster := true
			desc.IsMaster = &isMaster
		} else if self.BackupHostId == host.Id {
			isSlave := true
			desc.IsSlave = &isSlave
		}
	}

	if self.HostId != host.Id {
		desc.IsVolatileHost = true
	}

	// isolated devices
	isolatedDevs, _ := self.GetIsolatedDevices()
	for _, dev := range isolatedDevs {
		desc.IsolatedDevices = append(desc.IsolatedDevices, dev.getDesc())
	}

	if self.IsSchedulerNumaAllocate() {
		cpuNumaPin := make([]api.SCpuNumaPin, 0)
		cpuNumaPinStr := self.GetMetadata(ctx, api.VM_METADATA_CPU_NUMA_PIN, nil)
		cpuNumaPinJson, err := jsonutils.ParseString(cpuNumaPinStr)
		if err != nil {
			log.Errorf("failed parse cpu numa pin %s: %s", cpuNumaPinStr, err)
		} else {
			cpuNumaPinJson.Unmarshal(&cpuNumaPin)
			desc.CpuNumaPin = cpuNumaPin
		}
	}

	// nics, domain
	desc.Domain = options.Options.DNSDomain
	nics, _ := self.GetNetworks("")
	changed, _ := self.fixDefaultGatewayByNics(ctx, auth.AdminCredential(), nics)
	if changed {
		nics, _ = self.GetNetworks("")
	}
	for _, nic := range nics {
		nicDesc := nic.getJsonDescAtHost(ctx, host)
		desc.Nics = append(desc.Nics, nicDesc)
		if len(nicDesc.Domain) > 0 {
			desc.Domain = nicDesc.Domain
		}
		secgroupDesc := nic.getSecgroupDesc()
		if secgroupDesc != nil {
			if desc.NicSecgroups == nil {
				desc.NicSecgroups = make([]*api.GuestnetworkSecgroupDesc, 0)
			}
			desc.NicSecgroups = append(desc.NicSecgroups, secgroupDesc)
		}
	}

	{
		var prevNicDesc *api.GuestnetworkJsonDesc
		if len(desc.Nics) > 0 {
			prevNicDesc = desc.Nics[len(desc.Nics)-1]
		}
		// append tap nic
		tapNicDesc := self.getTapNicJsonDesc(ctx, prevNicDesc)
		if tapNicDesc != nil {
			desc.Nics = append(desc.Nics, tapNicDesc)
		}
	}

	// disks
	disks, _ := self.GetGuestDisks()
	for _, disk := range disks {
		diskDesc := disk.GetJsonDescAtHost(ctx, host)
		desc.Disks = append(desc.Disks, diskDesc)
	}

	cdroms, _ := self.getCdroms()
	for _, cdrom := range cdroms {
		cdromDesc := cdrom.getJsonDesc()
		desc.Cdroms = append(desc.Cdroms, cdromDesc)
	}
	if len(desc.Cdroms) > 0 {
		desc.Cdrom = desc.Cdroms[0]
	}

	//floppy
	floppys, _ := self.getFloppys()
	for _, floppy := range floppys {
		floppyDesc := floppy.getJsonDesc()
		desc.Floppys = append(desc.Floppys, floppyDesc)
	}

	// tenant
	tc, _ := self.GetTenantCache(ctx)
	if tc != nil {
		desc.Tenant = tc.GetName()
		desc.DomainId = tc.DomainId
		desc.ProjectDomain = tc.Domain
	}
	desc.TenantId = self.ProjectId

	keypair := self.getKeypair()
	if keypair != nil {
		desc.Keypair = keypair.Name
		desc.Pubkey = keypair.PublicKey
	}

	desc.NetworkRoles = self.getNetworkRoles()

	desc.Secgroups, _ = self.getSecgroupJson()

	desc.SecurityRules = self.getSecurityGroupsRules()
	desc.AdminSecurityRules = self.getAdminSecurityRules()

	desc.ExtraOptions = self.getExtraOptions(ctx)

	desc.Kvm = self.getKvmOptions()

	zone, _ := self.getZone()
	if zone != nil {
		desc.ZoneId = zone.Id
		desc.Zone = zone.Name
	}

	desc.OsName = self.GetOS()

	desc.Metadata, _ = self.GetAllMetadata(ctx, nil)

	userData, _ := desc.Metadata["user_data"]
	if len(userData) > 0 {
		decodeData, _ := userdata.Decode(userData)
		if len(decodeData) > 0 {
			userData = decodeData
		}
		desc.UserData = userData
	}
	desc.PendingDeleted = self.PendingDeleted

	// add scaling group
	sggs, err := ScalingGroupGuestManager.Fetch("", self.Id)
	if err == nil && len(sggs) > 0 {
		desc.ScalingGroupId = sggs[0].ScalingGroupId
	}

	return desc
}

func (self *SGuest) GetJsonDescAtBaremetal(ctx context.Context, host *SHost) *api.GuestJsonDesc {
	desc := &api.GuestJsonDesc{
		Name:        self.Name,
		Description: self.Description,
		UUID:        self.Id,
		Mem:         self.VmemSize,
		Cpu:         self.VcpuCount,
	}

	desc.DiskConfig = host.getDiskConfig()

	self.fixDefaultGateway(ctx, auth.AdminCredential())
	netifs := host.GetAllNetInterfaces()
	desc.Domain = options.Options.DNSDomain

	for _, nic := range netifs {
		nicDesc := nic.getServerJsonDesc()
		if len(nicDesc.Ip) > 0 {
			desc.Nics = append(desc.Nics, nicDesc)
			if len(nicDesc.Domain) > 0 {
				desc.Domain = nicDesc.Domain
			}
		} else {
			desc.NicsStandby = append(desc.NicsStandby, nicDesc)
		}
	}

	disks, _ := self.GetGuestDisks()
	for _, disk := range disks {
		diskDesc := disk.GetJsonDescAtHost(ctx, host)
		desc.Disks = append(desc.Disks, diskDesc)
	}

	tc, _ := self.GetTenantCache(ctx)
	if tc != nil {
		desc.Tenant = tc.GetName()
		desc.DomainId = tc.DomainId
		desc.ProjectDomain = tc.Domain
	}
	desc.TenantId = self.ProjectId

	keypair := self.getKeypair()
	if keypair != nil {
		desc.Keypair = keypair.Name
		desc.Pubkey = keypair.PublicKey
	}

	desc.NetworkRoles = self.getNetworkRoles()

	desc.SecurityRules = self.getSecurityGroupsRules()
	desc.AdminSecurityRules = self.getAdminSecurityRules()

	zone, _ := self.getZone()
	if zone != nil {
		desc.ZoneId = zone.Id
		desc.Zone = zone.Name
	}

	desc.OsName = self.GetOS()
	desc.Metadata, _ = self.GetAllMetadata(ctx, nil)

	desc.UserData, _ = desc.Metadata["user_data"]
	desc.PendingDeleted = self.PendingDeleted

	return desc
}

func (self *SGuest) getNetworkRoles() []string {
	key := db.Metadata.GetSysadminKey("network_role")
	roleStr := self.GetMetadata(context.Background(), key, auth.AdminCredential())
	if len(roleStr) > 0 {
		return strings.Split(roleStr, ",")
	}
	return nil
}

func (manager *SGuestManager) FetchGuestById(guestId string) *SGuest {
	guest, err := manager.FetchById(guestId)
	if err != nil {
		log.Errorf("FetchById fail %s", err)
		return nil
	}
	return guest.(*SGuest)
}

func (manager *SGuestManager) GetSpecShouldCheckStatus(query *jsonutils.JSONDict) (bool, error) {
	return true, nil
}

func (self *SGuest) GetSpec(checkStatus bool) *jsonutils.JSONDict {
	if checkStatus {
		if utils.IsInStringArray(self.Status, []string{api.VM_SCHEDULE_FAILED}) {
			return nil
		}
	}
	spec := jsonutils.NewDict()
	spec.Set("cpu", jsonutils.NewInt(int64(self.VcpuCount)))
	spec.Set("mem", jsonutils.NewInt(int64(self.VmemSize)))

	// get disk spec
	guestdisks, _ := self.GetGuestDisks()
	diskSpecs := jsonutils.NewArray()
	for _, guestdisk := range guestdisks {
		info := guestdisk.ToDiskInfo()
		diskSpec := jsonutils.NewDict()
		diskSpec.Set("size", jsonutils.NewInt(info.Size))
		diskSpec.Set("backend", jsonutils.NewString(info.Backend))
		diskSpec.Set("medium_type", jsonutils.NewString(info.MediumType))
		diskSpec.Set("disk_type", jsonutils.NewString(info.DiskType))
		diskSpecs.Add(diskSpec)
	}
	spec.Set("disk", diskSpecs)

	// get nic spec
	guestnics, _ := self.GetNetworks("")

	nicSpecs := jsonutils.NewArray()
	for _, guestnic := range guestnics {
		nicSpec := jsonutils.NewDict()
		net, _ := guestnic.GetNetwork()
		nicSpec.Set("bandwidth", jsonutils.NewInt(int64(guestnic.getBandwidth(net, nil))))
		t := "int"
		if guestnic.IsExit(net) {
			t = "ext"
		}
		nicSpec.Set("type", jsonutils.NewString(t))
		nicSpecs.Add(nicSpec)
	}
	spec.Set("nic", nicSpecs)

	// get isolate device spec
	guestgpus, _ := self.GetIsolatedDevices()
	gpuSpecs := []GpuSpec{}
	for _, guestgpu := range guestgpus {
		if strings.HasPrefix(guestgpu.DevType, "GPU") {
			gs := guestgpu.GetGpuSpec()
			gpuSpecs = append(gpuSpecs, *gs)
		}
	}

	spec.Set("gpu", jsonutils.Marshal(gpuSpecs))
	return spec
}

func (manager *SGuestManager) GetSpecIdent(spec *jsonutils.JSONDict) []string {
	cpuCount, _ := spec.Int("cpu")
	memSize, _ := spec.Int("mem")
	memSizeMB, _ := utils.GetSizeMB(fmt.Sprintf("%d", memSize), "M")
	specKeys := []string{
		fmt.Sprintf("cpu:%d", cpuCount),
		fmt.Sprintf("mem:%dM", memSizeMB),
	}

	countKey := func(kf func(*jsonutils.JSONDict) string, dataArray jsonutils.JSONObject) map[string]int64 {
		countMap := make(map[string]int64)
		datas, _ := dataArray.GetArray()
		for _, data := range datas {
			key := kf(data.(*jsonutils.JSONDict))
			if count, ok := countMap[key]; !ok {
				countMap[key] = 1
			} else {
				count++
				countMap[key] = count
			}
		}
		return countMap
	}

	kfuncs := map[string]func(*jsonutils.JSONDict) string{
		"disk": func(data *jsonutils.JSONDict) string {
			backend, _ := data.GetString("backend")
			mediumType, _ := data.GetString("medium_type")
			size, _ := data.Int("size")
			sizeGB, _ := utils.GetSizeGB(fmt.Sprintf("%d", size), "M")
			return fmt.Sprintf("disk:%s_%s_%dG", backend, mediumType, sizeGB)
		},
		"nic": func(data *jsonutils.JSONDict) string {
			typ, _ := data.GetString("type")
			bw, _ := data.Int("bandwidth")
			return fmt.Sprintf("nic:%s_%dM", typ, bw)
		},
		"gpu": func(data *jsonutils.JSONDict) string {
			vendor, _ := data.GetString("vendor")
			model, _ := data.GetString("model")
			return fmt.Sprintf("gpu:%s_%s", vendor, model)
		},
	}

	for sKey, kf := range kfuncs {
		sArrary, err := spec.Get(sKey)
		if err != nil {
			log.Errorf("Get key %s array error: %v", sKey, err)
			continue
		}
		for key, count := range countKey(kf, sArrary) {
			specKeys = append(specKeys, fmt.Sprintf("%sx%d", key, count))
		}
	}
	return specKeys
}

func (self *SGuest) GetGpuSpec() *GpuSpec {
	if len(self.InstanceType) == 0 {
		return nil
	}
	host, err := self.GetHost()
	if err != nil {
		return nil
	}
	zone, err := host.GetZone()
	if err != nil {
		return nil
	}
	q := ServerSkuManager.Query().Equals("name", self.InstanceType).Equals("cloudregion_id", zone.CloudregionId).IsNotEmpty("gpu_spec")
	sku := &SServerSku{}
	err = q.First(sku)
	if err != nil {
		return nil
	}
	return &GpuSpec{
		Model:  sku.GpuSpec,
		Amount: sku.GpuCount,
	}
}

func (self *SGuest) GetShortDesc(ctx context.Context) *jsonutils.JSONDict {
	desc := self.SVirtualResourceBase.GetShortDesc(ctx)
	desc.Set("mem", jsonutils.NewInt(int64(self.VmemSize)))
	desc.Set("cpu", jsonutils.NewInt(int64(self.VcpuCount)))

	desc.Set("status", jsonutils.NewString(self.Status))
	desc.Set("shutdown_mode", jsonutils.NewString(self.ShutdownMode))
	if len(self.InstanceType) > 0 {
		desc.Set("instance_type", jsonutils.NewString(self.InstanceType))
	}
	if gp := self.GetGpuSpec(); gp != nil {
		desc.Set("gpu_model", jsonutils.NewString(gp.Model))
		desc.Set("gpu_count", jsonutils.NewString(gp.Amount))
	}

	address := jsonutils.NewString(strings.Join(self.GetRealIPs(), ","))
	desc.Set("ip_addr", address)

	if len(self.OsType) > 0 {
		desc.Add(jsonutils.NewString(self.OsType), "os_type")
	}
	if osDist := self.GetMetadata(ctx, "os_distribution", nil); len(osDist) > 0 {
		desc.Add(jsonutils.NewString(osDist), "os_distribution")
	}
	if osVer := self.GetMetadata(ctx, "os_version", nil); len(osVer) > 0 {
		desc.Add(jsonutils.NewString(osVer), "os_version")
	}

	templateId := self.GetTemplateId()
	if len(templateId) > 0 {
		desc.Set("template_id", jsonutils.NewString(templateId))
	}
	extBw := self.getBandwidth(true)
	intBw := self.getBandwidth(false)
	if extBw > 0 {
		desc.Set("ext_bandwidth", jsonutils.NewInt(int64(extBw)))
	}
	if intBw > 0 {
		desc.Set("int_bandwidth", jsonutils.NewInt(int64(intBw)))
	}

	if len(self.OsType) > 0 {
		desc.Add(jsonutils.NewString(self.OsType), "os_type")
	}

	if len(self.ExternalId) > 0 {
		desc.Add(jsonutils.NewString(self.ExternalId), "externalId")
	}

	desc.Set("hypervisor", jsonutils.NewString(self.GetHypervisor()))

	host, _ := self.GetHost()

	spec := self.GetSpec(false)
	if self.GetHypervisor() == api.HYPERVISOR_BAREMETAL {
		if host != nil {
			hostSpec := host.GetSpec(false)
			hostSpecIdent := HostManager.GetSpecIdent(hostSpec)
			spec.Set("host_spec", jsonutils.NewString(strings.Join(hostSpecIdent, "/")))
		}
	}
	if spec != nil {
		desc.Update(spec)
	}

	var billingInfo SCloudBillingInfo

	if host != nil {
		desc.Set("host", jsonutils.NewString(host.Name))
		desc.Set("host_id", jsonutils.NewString(host.Id))
		billingInfo.SCloudProviderInfo = host.getCloudProviderInfo()
	}

	if len(self.BackupHostId) > 0 {
		backupHost := HostManager.FetchHostById(self.BackupHostId)
		if backupHost != nil {
			desc.Set("backup_host", jsonutils.NewString(backupHost.Name))
			desc.Set("backup_host_id", jsonutils.NewString(backupHost.Id))
		}
	}

	if priceKey := self.GetMetadata(ctx, "ext:price_key", nil); len(priceKey) > 0 {
		billingInfo.PriceKey = priceKey
	}

	billingInfo.SBillingBaseInfo = self.getBillingBaseInfo()

	desc.Update(jsonutils.Marshal(billingInfo))

	return desc
}

func (self *SGuest) saveOsType(userCred mcclient.TokenCredential, osType string) error {
	diff, err := db.Update(self, func() error {
		self.OsType = osType
		return nil
	})
	if err != nil {
		return err
	}
	db.OpsLog.LogEvent(self, db.ACT_UPDATE, diff, userCred)
	return err
}

type sDeployInfo struct {
	Os               string
	Account          string
	Key              string
	Distro           string
	Version          string
	Arch             string
	Language         string
	TelegrafDeployed bool
	CurrentVersion   string
}

func (self *SGuest) SaveDeployInfo(ctx context.Context, userCred mcclient.TokenCredential, data jsonutils.JSONObject) {
	deployInfo := sDeployInfo{}
	data.Unmarshal(&deployInfo)
	info := make(map[string]interface{})
	if len(deployInfo.Os) > 0 {
		self.saveOsType(userCred, deployInfo.Os)
		info["os_name"] = deployInfo.Os
	}
	driver, _ := self.GetDriver()
	if len(deployInfo.Account) > 0 {
		info["login_account"] = deployInfo.Account
		if len(deployInfo.Key) > 0 {
			info["login_key"] = deployInfo.Key
			if len(self.KeypairId) > 0 && (driver != nil && !driver.IsSupportdDcryptPasswordFromSecretKey()) { // Tencent Cloud does not support simultaneous setting of secret keys and passwords
				info["login_key"], _ = seclib2.EncryptBase64(self.GetKeypairPublicKey(), "")
			}
			info["login_key_timestamp"] = timeutils.UtcNow()
		} else {
			info["login_key"] = "none"
			info["login_key_timestamp"] = "none"
		}
	}
	if len(deployInfo.Distro) > 0 {
		info["os_distribution"] = deployInfo.Distro
	}
	if len(deployInfo.Version) > 0 {
		info["os_version"] = deployInfo.Version
	}
	if len(deployInfo.Arch) > 0 {
		info["os_arch"] = deployInfo.Arch
	}
	if len(deployInfo.Language) > 0 {
		info["os_language"] = deployInfo.Language
	}
	if deployInfo.TelegrafDeployed {
		info["telegraf_deployed"] = true
	}
	if len(deployInfo.CurrentVersion) > 0 {
		info["current_version"] = deployInfo.CurrentVersion
	}
	self.SetAllMetadata(ctx, info, userCred)
	self.saveOldPassword(ctx, userCred)
}

func (self *SGuest) isAllDisksReady() bool {
	ready := true
	disks, _ := self.GetGuestDisks()
	if disks == nil || len(disks) == 0 {
		return true
	}
	for i := 0; i < len(disks); i += 1 {
		disk := disks[i].GetDisk()
		if !(disk.isReady() || disk.Status == api.DISK_START_MIGRATE) {
			ready = false
			break
		}
	}
	return ready
}

func (self *SGuest) GetKeypairPublicKey() string {
	keypair := self.getKeypair()
	if keypair != nil {
		return keypair.PublicKey
	}
	return ""
}

func (self *SGuest) GetKeypair() *SKeypair {
	return self.getKeypair()
}

func (manager *SGuestManager) GetIpsInProjectWithName(projectId, name string, isExitOnly bool, addrType api.TAddressType) []string {
	name = strings.TrimSuffix(name, ".")

	ipField := "ip_addr"
	gwField := "guest_gateway"
	if addrType == api.AddressTypeIPv6 {
		ipField = "ip6_addr"
		gwField = "guest_gateway6"
	}

	guestnics := GuestnetworkManager.Query().IsNotEmpty(ipField).SubQuery()
	guestsQ := manager.Query().IsFalse("pending_deleted").Equals("hostname", name)
	if len(projectId) > 0 {
		guestsQ = guestsQ.Equals("tenant_id", projectId)
	}
	guests := guestsQ.SubQuery()
	networks := NetworkManager.Query().IsNotNull(gwField).SubQuery()

	q := guestnics.Query(guestnics.Field(ipField))
	q = q.Join(guests, sqlchemy.Equals(guests.Field("id"), guestnics.Field("guest_id")))
	q = q.Join(networks, sqlchemy.Equals(networks.Field("id"), guestnics.Field("network_id")))

	ips := make([]string, 0)
	rows, err := q.Rows()
	if err != nil {
		log.Errorf("Get guest ip with name query err: %v", err)
		return ips
	}
	defer rows.Close()
	for rows.Next() {
		var ip string
		err = rows.Scan(&ip)
		if err != nil {
			log.Errorf("Get guest ip with name scan err: %v", err)
			return ips
		}
		ips = append(ips, ip)
	}
	if addrType == api.AddressTypeIPv6 {
		return ips
	}
	return manager.getIpsByExit(ips, isExitOnly)
}

func (manager *SGuestManager) getIpsByExit(ips []string, isExitOnly bool) []string {
	intRet := make([]string, 0)
	extRet := make([]string, 0)
	for _, ip := range ips {
		addr, _ := netutils.NewIPV4Addr(ip)
		if netutils.IsExitAddress(addr) {
			extRet = append(extRet, ip)
			continue
		}
		intRet = append(intRet, ip)
	}
	if isExitOnly {
		return extRet
	} else if len(intRet) > 0 {
		return intRet
	}
	return extRet
}

func (manager *SGuestManager) getExpiredPendingDeleteGuests() []SGuest {
	deadline := time.Now().Add(time.Duration(options.Options.PendingDeleteExpireSeconds*-1) * time.Second)

	q := manager.Query()
	q = q.IsTrue("pending_deleted").LT("pending_deleted_at", deadline).Limit(options.Options.PendingDeleteMaxCleanBatchSize)

	guests := make([]SGuest, 0)
	err := db.FetchModelObjects(GuestManager, q, &guests)
	if err != nil {
		log.Errorf("fetch guests error %s", err)
		return nil
	}

	return guests
}

func (manager *SGuestManager) CleanPendingDeleteServers(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
	guests := manager.getExpiredPendingDeleteGuests()
	if guests == nil {
		return
	}
	for i := 0; i < len(guests); i += 1 {
		opts := api.ServerDeleteInput{
			OverridePendingDelete: true,
			DeleteSnapshots:       options.Options.DeleteSnapshotExpiredRelease,
			DeleteEip:             options.Options.DeleteEipExpiredRelease,
			DeleteDisks:           options.Options.DeleteDisksExpiredRelease,
		}
		// 跳过单独在云上开机过的虚拟机，避免误清理
		if len(guests[i].GetExternalId()) > 0 {
			iVm, err := guests[i].GetIVM(ctx)
			if err == nil && iVm.GetStatus() == api.VM_RUNNING {
				if guests[i].Status != api.VM_DELETE_FAIL {
					guests[i].SetStatus(ctx, userCred, api.VM_DELETE_FAIL, "vm status is running")
				}
				continue
			}
		}
		guests[i].StartDeleteGuestTask(ctx, userCred, "", opts)
	}
}

func (manager *SGuestManager) getExpiredPrepaidGuests() []SGuest {
	deadline := time.Now().Add(time.Duration(options.Options.PrepaidExpireCheckSeconds*-1) * time.Second)

	q := manager.Query()
	q = q.Equals("billing_type", billing_api.BILLING_TYPE_PREPAID).LT("expired_at", deadline).
		IsFalse("pending_deleted").Limit(options.Options.ExpiredPrepaidMaxCleanBatchSize)

	guests := make([]SGuest, 0)
	err := db.FetchModelObjects(GuestManager, q, &guests)
	if err != nil {
		log.Errorf("fetch guests error %s", err)
		return nil
	}

	return guests
}

func (manager *SGuestManager) getNeedRenewPrepaidGuests() ([]SGuest, error) {
	deadline := time.Now().Add(time.Duration(options.Options.PrepaidAutoRenewHours)*time.Hour + 20*time.Minute)

	q := manager.Query()
	q = q.Equals("billing_type", billing_api.BILLING_TYPE_PREPAID).LT("expired_at", deadline).
		IsFalse("pending_deleted").In("hypervisor", GetNotSupportAutoRenewHypervisors()).IsTrue("auto_renew")

	guests := make([]SGuest, 0)
	err := db.FetchModelObjects(GuestManager, q, &guests)
	if err != nil {
		return nil, errors.Wrap(err, "db.FetchModelObjects")
	}

	return guests, nil
}

func (manager *SGuestManager) getExpiredPostpaidGuests() []SGuest {
	q := ListExpiredPostpaidResources(manager.Query(), options.Options.ExpiredPrepaidMaxCleanBatchSize)
	q = q.IsFalse("pending_deleted")
	guests := make([]SGuest, 0)
	err := db.FetchModelObjects(GuestManager, q, &guests)
	if err != nil {
		log.Errorf("fetch guests error %s", err)
		return nil
	}

	return guests
}

func (self *SGuest) doExternalSync(ctx context.Context, userCred mcclient.TokenCredential) error {
	host, _ := self.GetHost()
	if host == nil {
		return fmt.Errorf("no host???")
	}
	ihost, iprovider, err := host.GetIHostAndProvider(ctx)
	if err != nil {
		return err
	}
	iVM, err := ihost.GetIVMById(self.ExternalId)
	if err != nil {
		return err
	}
	return self.syncWithCloudVM(ctx, userCred, iprovider, host, iVM, nil, true)
}

func (manager *SGuestManager) DeleteExpiredPrepaidServers(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
	guests := manager.getExpiredPrepaidGuests()
	if guests == nil {
		return
	}
	for i := 0; i < len(guests); i += 1 {
		// fake delete expired prepaid servers
		if len(guests[i].ExternalId) > 0 {
			err := guests[i].doExternalSync(ctx, userCred)
			if err == nil && guests[i].IsValidPrePaid() {
				continue
			}
		}
		guests[i].SetDisableDelete(userCred, false)
		opts := api.ServerDeleteInput{
			DeleteSnapshots: options.Options.DeleteSnapshotExpiredRelease,
			DeleteEip:       options.Options.DeleteEipExpiredRelease,
			DeleteDisks:     options.Options.DeleteDisksExpiredRelease,
		}
		guests[i].StartDeleteGuestTask(ctx, userCred, "", opts)
	}
}

func (manager *SGuestManager) AutoRenewPrepaidServer(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
	guests, err := manager.getNeedRenewPrepaidGuests()
	if err != nil {
		log.Errorf("failed to get need renew prepaid guests error: %v", err)
		return
	}
	for i := 0; i < len(guests); i += 1 {
		drv, err := guests[i].GetDriver()
		if err != nil {
			continue
		}
		if len(guests[i].ExternalId) > 0 && !drv.IsSupportSetAutoRenew() {
			err := guests[i].doExternalSync(ctx, userCred)
			if err == nil && guests[i].IsValidPrePaid() {
				continue
			}
		}
		guests[i].startGuestRenewTask(ctx, userCred, guests[i].BillingCycle, "")
	}
}

func (manager *SGuestManager) DeleteExpiredPostpaidServers(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
	guests := manager.getExpiredPostpaidGuests()
	if len(guests) == 0 {
		log.Infof("No expired postpaid guest")
		return
	}
	for i := 0; i < len(guests); i++ {
		if len(guests[i].ExternalId) > 0 {
			err := guests[i].doExternalSync(ctx, userCred)
			if err == nil && guests[i].IsValidPostPaid() {
				continue
			}
		}
		guests[i].SetDisableDelete(userCred, false)
		opts := api.ServerDeleteInput{
			DeleteSnapshots: options.Options.DeleteSnapshotExpiredRelease,
			DeleteEip:       options.Options.DeleteEipExpiredRelease,
			DeleteDisks:     options.Options.DeleteDisksExpiredRelease,
		}
		guests[i].StartDeleteGuestTask(ctx, userCred, "", opts)
	}
}

func (self *SGuest) IsEipAssociable() error {
	if !utils.IsInStringArray(self.Status, []string{api.VM_READY, api.VM_RUNNING}) {
		return errors.Wrapf(httperrors.ErrInvalidStatus, "cannot associate eip in status %s", self.Status)
	}

	err := ValidateAssociateEip(self)
	if err != nil {
		return errors.Wrap(err, "ValidateAssociateEip")
	}

	var eip *SElasticip
	switch self.Hypervisor {
	case api.HYPERVISOR_AWS:
		eip, err = self.GetElasticIp()
	default:
		eip, err = self.GetEipOrPublicIp()
	}

	if err != nil {
		log.Errorf("Fail to get Eip %s", err)
		return errors.Wrap(err, "IsEipAssociable")
	}

	if eip != nil {
		return httperrors.NewInvalidStatusError("already associate with eip")
	}

	return nil
}

func (self *SGuest) GetEipOrPublicIp() (*SElasticip, error) {
	return ElasticipManager.getEip(api.EIP_ASSOCIATE_TYPE_SERVER, self.Id, "")
}

func (self *SGuest) GetElasticIp() (*SElasticip, error) {
	return ElasticipManager.getEip(api.EIP_ASSOCIATE_TYPE_SERVER, self.Id, api.EIP_MODE_STANDALONE_EIP)
}

func (self *SGuest) GetPublicIp() (*SElasticip, error) {
	return ElasticipManager.getEip(api.EIP_ASSOCIATE_TYPE_SERVER, self.Id, api.EIP_MODE_INSTANCE_PUBLICIP)
}

func (self *SGuest) SyncVMEip(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, extEip cloudprovider.ICloudEIP, syncOwnerId mcclient.IIdentityProvider) compare.SyncResult {
	result := compare.SyncResult{}

	eip, err := self.GetEipOrPublicIp()
	if err != nil {
		result.Error(fmt.Errorf("GetEipOrPublicIp error %s", err))
		return result
	}

	region, err := self.getRegion()
	if err != nil {
		result.Error(fmt.Errorf("getRegion error %s", err))
		return result
	}
	if eip == nil && extEip == nil {
		// do nothing
		return result
	}
	if eip == nil && extEip != nil {
		// add
		neip, err := ElasticipManager.getEipByExtEip(ctx, userCred, extEip, provider, region, syncOwnerId)
		if err != nil {
			result.AddError(errors.Wrapf(err, "getEipByExtEip"))
			return result
		}
		err = neip.AssociateInstance(ctx, userCred, api.EIP_ASSOCIATE_TYPE_SERVER, self)
		if err != nil {
			result.AddError(errors.Wrapf(err, "neip.AssociateInstance"))
			return result
		}
		result.Add()
		return result
	}
	if eip != nil && extEip == nil {
		// remove
		err = eip.Dissociate(ctx, userCred)
		if err != nil {
			result.DeleteError(err)
			return result
		}
		result.Delete()
		return result
	}
	// sync
	if eip.IpAddr != extEip.GetIpAddr() {
		// remove then add
		err = eip.Dissociate(ctx, userCred)
		if err != nil {
			// fail to remove
			result.DeleteError(err)
			return result
		}
		result.Delete()
		neip, err := ElasticipManager.getEipByExtEip(ctx, userCred, extEip, provider, region, syncOwnerId)
		if err != nil {
			result.AddError(err)
			return result
		}
		err = neip.AssociateInstance(ctx, userCred, api.EIP_ASSOCIATE_TYPE_SERVER, self)
		if err != nil {
			result.AddError(err)
		} else {
			result.Add()
		}
		return result
	}
	// do nothing
	err = eip.SyncWithCloudEip(ctx, userCred, provider, extEip, syncOwnerId)
	if err != nil {
		result.UpdateError(err)
	} else {
		result.Update()
	}
	return result
}

func (self *SGuest) getSecgroupsBySecgroupExternalIds(externalIds []string) ([]SSecurityGroup, error) {
	vpc, err := self.GetVpc()
	if err != nil {
		return nil, errors.Wrapf(err, "GetVpc")
	}
	region, err := vpc.GetRegion()
	if err != nil {
		return nil, errors.Wrapf(err, "GetRegion")
	}
	filter, err := region.GetDriver().GetSecurityGroupFilter(vpc)
	if err != nil {
		return nil, errors.Wrapf(err, "GetSecurityGroupFilter")
	}

	q := SecurityGroupManager.Query().In("external_id", externalIds)
	q = filter(q)
	secgroups := []SSecurityGroup{}
	err = db.FetchModelObjects(SecurityGroupManager, q, &secgroups)
	if err != nil {
		return nil, errors.Wrapf(err, "db.FetchModelObjects")
	}
	return secgroups, nil
}

func (self *SGuest) SyncVMSecgroups(ctx context.Context, userCred mcclient.TokenCredential, externalIds []string) error {
	// clear secgroup if vm not support security group
	drv, err := self.GetDriver()
	if err != nil {
		return err
	}
	if drv.GetMaxSecurityGroupCount() == 0 || len(externalIds) == 0 {
		_, err := db.Update(self, func() error {
			self.SecgrpId = ""
			self.AdminSecgrpId = ""
			return nil
		})
		return err
	}

	secgroups, err := self.getSecgroupsBySecgroupExternalIds(externalIds)
	if err != nil {
		return errors.Wrap(err, "getSecgroupsBySecgroupExternalIds")
	}
	secgroupIds := []string{}
	for _, secgroup := range secgroups {
		secgroupIds = append(secgroupIds, secgroup.Id)
	}

	return self.SaveSecgroups(ctx, userCred, secgroupIds)
}

func (self *SGuest) GetIVM(ctx context.Context) (cloudprovider.ICloudVM, error) {
	if len(self.ExternalId) == 0 {
		return nil, errors.Wrapf(cloudprovider.ErrNotFound, "empty externalId")
	}
	host, err := self.GetHost()
	if err != nil {
		return nil, errors.Wrapf(err, "GetHost")
	}
	iregion, err := host.GetIRegion(ctx)
	if err != nil {
		return nil, errors.Wrapf(err, "GetIRegion")
	}
	ihost, err := iregion.GetIHostById(host.ExternalId)
	if err != nil {
		return nil, errors.Wrapf(err, "GetIHost")
	}
	ivm, err := ihost.GetIVMById(self.ExternalId)
	if err != nil {
		if errors.Cause(err) != cloudprovider.ErrNotFound {
			return nil, errors.Wrapf(err, "GetIVMById(%s)", self.ExternalId)
		}
		return iregion.GetIVMById(self.ExternalId)
	}
	return ivm, nil
}

func (self *SGuest) PendingDetachScalingGroup() error {
	sggs, err := ScalingGroupGuestManager.Fetch("", self.GetId())
	if err != nil {
		return err
	}
	for i := range sggs {
		sggs[i].SetGuestStatus(api.SG_GUEST_STATUS_PENDING_REMOVE)
	}
	return nil
}

func (self *SGuest) DeleteEip(ctx context.Context, userCred mcclient.TokenCredential) error {
	eip, err := self.GetEipOrPublicIp()
	if err != nil {
		log.Errorf("Delete eip fail for get Eip %s", err)
		return err
	}
	if eip == nil {
		return nil
	}
	if eip.Mode == api.EIP_MODE_INSTANCE_PUBLICIP {
		err = eip.RealDelete(ctx, userCred)
		if err != nil {
			log.Errorf("Delete eip on delete server fail %s", err)
			return err
		}
	} else {
		err = eip.Dissociate(ctx, userCred)
		if err != nil {
			log.Errorf("Dissociate eip on delete server fail %s", err)
			return err
		}
	}
	return nil
}

func (self *SGuest) SetDisableDelete(userCred mcclient.TokenCredential, val bool) error {
	diff, err := db.Update(self, func() error {
		if val {
			self.DisableDelete = tristate.True
		} else {
			self.DisableDelete = tristate.False
		}
		return nil
	})
	if err != nil {
		return err
	}
	db.OpsLog.LogEvent(self, db.ACT_UPDATE, diff, userCred)
	logclient.AddSimpleActionLog(self, logclient.ACT_UPDATE, diff, userCred, true)
	return err
}

func (self *SGuest) getDefaultStorageType() string {
	diskCat := self.CategorizeDisks()
	if diskCat.Root != nil {
		rootStorage, _ := diskCat.Root.GetStorage()
		if rootStorage != nil {
			return rootStorage.StorageType
		}
	}
	return api.STORAGE_LOCAL
}

func (self *SGuest) GetApptags() []string {
	tagsStr := self.GetMetadata(context.Background(), api.VM_METADATA_APP_TAGS, nil)
	if len(tagsStr) > 0 {
		return strings.Split(tagsStr, ",")
	}
	return nil
}

func (self *SGuest) ToSchedDesc() *schedapi.ScheduleInput {
	desc := new(schedapi.ScheduleInput)
	config := &schedapi.ServerConfig{
		Name:          self.Name,
		Memory:        self.VmemSize,
		Ncpu:          int(self.VcpuCount),
		ServerConfigs: new(api.ServerConfigs),
	}
	desc.Id = self.Id
	self.FillGroupSchedDesc(config.ServerConfigs)
	self.FillDiskSchedDesc(config.ServerConfigs)
	self.FillNetSchedDesc(config.ServerConfigs)
	if len(self.HostId) > 0 && regutils.MatchUUID(self.HostId) {
		desc.HostId = self.HostId
	}
	config.Project = self.ProjectId
	config.Domain = self.DomainId
	/*tags := self.GetApptags()
	for i := 0; i < len(tags); i++ {
		desc.Set(tags[i], jsonutils.JSONTrue)
	}*/

	config.Hypervisor = self.GetHypervisor()
	desc.ServerConfig = *config
	desc.OsArch = self.OsArch
	desc.ExtraCpuCount = self.ExtraCpuCount
	return desc
}

func (self *SGuest) FillGroupSchedDesc(desc *api.ServerConfigs) {
	groups := make([]SGroupguest, 0)
	err := GroupguestManager.Query().Equals("guest_id", self.Id).All(&groups)
	if err != nil {
		log.Errorln(err)
		return
	}
	groupids := make([]string, len(groups))
	for i := range groups {
		groupids[i] = groups[i].GroupId
	}
	desc.InstanceGroupIds = groupids
}

func (self *SGuest) FillDiskSchedDesc(desc *api.ServerConfigs) {
	guestDisks := make([]SGuestdisk, 0)
	err := GuestdiskManager.Query().Equals("guest_id", self.Id).All(&guestDisks)
	if err != nil {
		log.Errorf("FillDiskSchedDesc: %v", err)
		return
	}
	for i := 0; i < len(guestDisks); i++ {
		diskConf := guestDisks[i].ToDiskConfig()
		// HACK: storage used by self, so earse it
		if !utils.IsInStringArray(diskConf.Backend, api.SHARED_STORAGE) {
			diskConf.Storage = ""
		}
		desc.Disks = append(desc.Disks, diskConf)
	}
}

func (self *SGuest) FillNetSchedDesc(desc *api.ServerConfigs) {
	guestNetworks := make([]SGuestnetwork, 0)
	err := GuestnetworkManager.Query().Equals("guest_id", self.Id).All(&guestNetworks)
	if err != nil {
		log.Errorf("FillNetSchedDesc: %v", err)
		return
	}
	if desc.Networks == nil {
		desc.Networks = make([]*api.NetworkConfig, 0)
	}
	for i := 0; i < len(guestNetworks); i++ {
		desc.Networks = append(desc.Networks, guestNetworks[i].ToNetworkConfig())
	}
}

func (self *SGuest) GuestDisksHasSnapshot() (bool, error) {
	guestDisks, err := self.GetGuestDisks()
	if err != nil {
		return false, errors.Wrapf(err, "GetGuestDisks")
	}
	for i := 0; i < len(guestDisks); i++ {
		cnt, err := SnapshotManager.GetDiskSnapshotCount(guestDisks[i].DiskId)
		if err != nil {
			return false, err
		}
		if cnt > 0 {
			return true, nil
		}
	}
	return false, nil
}

func (self *SGuest) OnScheduleToHost(ctx context.Context, userCred mcclient.TokenCredential, hostId string) error {
	err := self.SetHostId(userCred, hostId)
	if err != nil {
		return err
	}

	notes := jsonutils.NewDict()
	notes.Add(jsonutils.NewString(hostId), "host_id")
	db.OpsLog.LogEvent(self, db.ACT_SCHEDULE, notes, userCred)

	host, _ := self.GetHost()
	return host.ClearSchedDescCache()
}

func (guest *SGuest) GetDetailsTasks(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
	since := time.Time{}
	if query.Contains("since") {
		since, _ = query.GetTime("since")
	}
	var isOpen *bool = nil
	if query.Contains("is_open") {
		isOpenVal, _ := query.Bool("is_open")
		isOpen = &isOpenVal
	}
	q := taskman.TaskManager.QueryTasksOfObject(guest, since, isOpen)
	objs, err := db.Query2List(taskman.TaskManager, ctx, userCred, q, query, false)
	if err != nil {
		return nil, err
	}
	ret := jsonutils.NewDict()
	ret.Add(jsonutils.NewArray(objs...), "tasks")
	return ret, nil
}

func (guest *SGuest) GetDynamicConditionInput() *jsonutils.JSONDict {
	return guest.ToSchedDesc().ToConditionInput()
}

func (self *SGuest) ToCreateInput(ctx context.Context, userCred mcclient.TokenCredential) *api.ServerCreateInput {
	genInput := self.toCreateInput()
	userInput, err := self.GetCreateParams(ctx, userCred)
	if err != nil {
		return genInput
	}
	if self.GetHypervisor() != api.HYPERVISOR_BAREMETAL {
		// fill missing create params like schedtags
		disks := []*api.DiskConfig{}
		for idx, disk := range genInput.Disks {
			tmpD := disk
			if idx < len(userInput.Disks) {
				inputDisk := userInput.Disks[idx]
				tmpD.Schedtags = inputDisk.Schedtags
				tmpD.Storage = inputDisk.Storage
			}
			disks = append(disks, tmpD)
		}
		userInput.Disks = disks
	}
	nets := []*api.NetworkConfig{}
	for idx, net := range genInput.Networks {
		tmpN := net
		if idx < len(userInput.Networks) {
			inputNet := userInput.Networks[idx]
			tmpN.Schedtags = inputNet.Schedtags
			tmpN.Network = inputNet.Network
		}
		nets = append(nets, tmpN)
	}
	userInput.Networks = nets
	userInput.IsolatedDevices = genInput.IsolatedDevices
	userInput.Count = 1
	// override some old userInput properties via genInput because of change config behavior
	userInput.VmemSize = genInput.VmemSize
	userInput.VcpuCount = genInput.VcpuCount
	userInput.Vga = genInput.Vga
	userInput.Vdi = genInput.Vdi
	userInput.Bios = genInput.Bios
	userInput.Cdrom = genInput.Cdrom
	userInput.Description = genInput.Description
	userInput.BootOrder = genInput.BootOrder
	userInput.DisableDelete = genInput.DisableDelete
	userInput.ShutdownBehavior = genInput.ShutdownBehavior
	userInput.IsSystem = genInput.IsSystem
	userInput.SecgroupId = genInput.SecgroupId
	userInput.KeypairId = genInput.KeypairId
	userInput.EipBw = genInput.EipBw
	userInput.EipChargeType = genInput.EipChargeType
	drv, _ := self.GetDriver()
	if drv != nil && drv.IsSupportPublicIp() {
		userInput.PublicIpBw = genInput.PublicIpBw
		userInput.PublicIpChargeType = genInput.PublicIpChargeType
	}
	userInput.AutoRenew = genInput.AutoRenew
	// cloned server should belongs to the project creating it
	userInput.ProjectId = userCred.GetProjectId()
	userInput.ProjectDomainId = userCred.GetProjectDomainId()
	userInput.Secgroups = []string{}
	secgroups, _ := self.GetSecgroups()
	for _, secgroup := range secgroups {
		userInput.Secgroups = append(userInput.Secgroups, secgroup.Id)
	}
	if genInput.ResourceType != "" {
		userInput.ResourceType = genInput.ResourceType
	}
	if genInput.InstanceType != "" {
		userInput.InstanceType = genInput.InstanceType
	}
	if genInput.PreferRegion != "" {
		userInput.PreferRegion = genInput.PreferRegion
	}
	if genInput.PreferZone != "" {
		userInput.PreferZone = genInput.PreferZone
	}
	// clean some of user input
	userInput.GenerateName = ""
	userInput.Description = ""
	userInput.Hostname = ""
	return userInput
}

func (self *SGuest) toCreateInput() *api.ServerCreateInput {
	r := new(api.ServerCreateInput)
	r.VmemSize = self.VmemSize
	r.VcpuCount = int(self.VcpuCount)
	if guestCdrom := self.getCdrom(false, 0); guestCdrom != nil {
		r.Cdrom = guestCdrom.ImageId
	}
	r.Vga = self.Vga
	r.Vdi = self.Vdi
	r.Bios = self.Bios
	r.Description = self.Description
	r.BootOrder = self.BootOrder
	r.DisableDelete = new(bool)
	*r.DisableDelete = self.DisableDelete.Bool()
	r.ShutdownBehavior = self.ShutdownBehavior
	// ignore r.DeployConfigs
	r.IsSystem = &self.IsSystem
	r.SecgroupId = self.SecgrpId

	r.ServerConfigs = new(api.ServerConfigs)
	r.Hypervisor = self.Hypervisor
	r.InstanceType = self.InstanceType
	r.ProjectId = self.ProjectId
	r.ProjectDomainId = self.DomainId
	r.Count = 1
	r.Disks = self.ToDisksConfig()
	r.Networks = self.ToNetworksConfig()
	r.IsolatedDevices = self.ToIsolatedDevicesConfig()
	r.AutoRenew = self.AutoRenew

	if keypair := self.getKeypair(); keypair != nil {
		r.KeypairId = keypair.Id
	}
	if host, _ := self.GetHost(); host != nil {
		r.ResourceType = host.ResourceType
	}
	if eip, _ := self.GetEipOrPublicIp(); eip != nil {
		switch eip.Mode {
		case api.EIP_MODE_STANDALONE_EIP:
			r.EipBw = eip.Bandwidth
			r.EipChargeType = eip.ChargeType
		case api.EIP_MODE_INSTANCE_PUBLICIP:
			drv, _ := self.GetDriver()
			if drv != nil && drv.IsSupportPublicIp() {
				r.PublicIpBw = eip.Bandwidth
				r.PublicIpChargeType = eip.ChargeType
			}
		}
	}
	if zone, _ := self.getZone(); zone != nil {
		region, _ := zone.GetRegion()
		r.PreferRegion = region.GetId()
		r.PreferZone = zone.GetId()
	}
	return r
}

func (self *SGuest) ToDisksConfig() []*api.DiskConfig {
	guestDisks, err := self.GetGuestDisks()
	if err != nil {
		return nil
	}
	ret := make([]*api.DiskConfig, len(guestDisks))
	for idx, guestDisk := range guestDisks {
		diskConf := new(api.DiskConfig)
		disk := guestDisk.GetDisk()
		diskConf.Index = int(guestDisk.Index)
		diskConf.ImageId = disk.GetTemplateId()
		diskConf.SnapshotId = disk.SnapshotId
		diskConf.DiskType = disk.DiskType
		diskConf.SizeMb = disk.DiskSize
		diskConf.Fs = disk.FsFormat
		diskConf.Format = disk.DiskFormat
		diskConf.Driver = guestDisk.Driver
		diskConf.Cache = guestDisk.CacheMode
		diskConf.Mountpoint = guestDisk.Mountpoint
		storage, _ := disk.GetStorage()
		diskConf.Backend = storage.StorageType
		diskConf.Medium = storage.MediumType
		ret[idx] = diskConf
	}
	return ret
}

func (self *SGuest) ToNetworksConfig() []*api.NetworkConfig {
	guestNetworks, _ := self.GetNetworks("")
	if len(guestNetworks) == 0 {
		return nil
	}
	ret := make([]*api.NetworkConfig, 0)
	teamMacs := []string{}
	for _, gn := range guestNetworks {
		if tg, _ := gn.GetTeamGuestnetwork(); tg != nil {
			teamMacs = append(teamMacs, gn.TeamWith)
		}
	}
	for _, guestNetwork := range guestNetworks {
		netConf := new(api.NetworkConfig)
		network, err := guestNetwork.GetNetwork()
		if err != nil {
			continue
		}
		requireTeaming := false
		if tg, _ := guestNetwork.GetTeamGuestnetwork(); tg != nil {
			requireTeaming = true
		}
		if utils.IsInStringArray(guestNetwork.MacAddr, teamMacs) {
			continue
		}

		// XXX: same wire
		netConf.Wire = network.WireId
		netConf.Network = network.Id
		netConf.Exit = guestNetwork.IsExit(nil)
		if len(guestNetwork.Ip6Addr) > 0 {
			netConf.RequireIPv6 = true
			if len(guestNetwork.IpAddr) == 0 {
				netConf.StrictIPv6 = true
			}
		}
		// netConf.Private
		// netConf.Reserved
		netConf.Driver = guestNetwork.Driver
		netConf.BwLimit = guestNetwork.BwLimit
		netConf.RequireTeaming = requireTeaming
		// netConf.NetType
		ret = append(ret, netConf)
	}
	return ret
}

func (self *SGuest) ToIsolatedDevicesConfig() []*api.IsolatedDeviceConfig {
	guestIsolatedDevices, _ := self.GetIsolatedDevices()
	if len(guestIsolatedDevices) == 0 {
		return nil
	}
	ret := make([]*api.IsolatedDeviceConfig, len(guestIsolatedDevices))
	for idx, guestIsolatedDevice := range guestIsolatedDevices {
		devConf := new(api.IsolatedDeviceConfig)
		devConf.Model = guestIsolatedDevice.Model
		devConf.Vendor = guestIsolatedDevice.getVendor()
		devConf.DevType = guestIsolatedDevice.DevType
		ret[idx] = devConf
	}
	return ret
}

func (self *SGuest) IsImport(ctx context.Context, userCred mcclient.TokenCredential) bool {
	return self.GetMetadata(ctx, "__is_import", userCred) == "true"
}

func (guest *SGuest) GetDetailsRemoteNics(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
	iVM, err := guest.GetIVM(ctx)
	if err != nil {
		return nil, httperrors.NewGeneralError(err)
	}
	iNics, err := iVM.GetINics()
	if err != nil {
		return nil, httperrors.NewGeneralError(err)
	}
	type SVNic struct {
		Index  int
		Ip     string
		Mac    string
		Driver string
	}
	nics := make([]SVNic, len(iNics))
	for i := range iNics {
		nics[i] = SVNic{
			Index:  i,
			Ip:     iNics[i].GetIP(),
			Mac:    iNics[i].GetMAC(),
			Driver: iNics[i].GetDriver(),
		}
	}
	// ret := jsonutils.NewDict()
	// ret.Set("vnics", jsonutils.Marshal(nics))
	return jsonutils.Marshal(nics), nil
}

func (self *SGuest) GetInstanceSnapshots() ([]SInstanceSnapshot, error) {
	instanceSnapshots := make([]SInstanceSnapshot, 0)
	q := InstanceSnapshotManager.Query().Equals("guest_id", self.Id)
	err := db.FetchModelObjects(InstanceSnapshotManager, q, &instanceSnapshots)
	if err != nil {
		return nil, err
	}
	return instanceSnapshots, nil
}

func (self *SGuest) GetInstanceSnapshotCount() (int, error) {
	q := InstanceSnapshotManager.Query().Equals("guest_id", self.Id)
	return q.CountWithError()
}

func (self *SGuest) GetDiskSnapshotsNotInInstanceSnapshots() ([]SSnapshot, error) {
	guestDisks, err := self.GetGuestDisks()
	if err != nil {
		return nil, errors.Wrapf(err, "GetGuestDisks")
	}
	diskIds := make([]string, len(guestDisks))
	for i := 0; i < len(guestDisks); i++ {
		diskIds[i] = guestDisks[i].DiskId
	}
	snapshots := make([]SSnapshot, 0)
	q := SnapshotManager.Query().IsFalse("fake_deleted").In("disk_id", diskIds)
	sq := InstanceSnapshotJointManager.Query("snapshot_id").SubQuery()
	q = q.LeftJoin(sq, sqlchemy.Equals(q.Field("id"), sq.Field("snapshot_id"))).
		Filter(sqlchemy.IsNull(sq.Field("snapshot_id")))
	err = db.FetchModelObjects(SnapshotManager, q, &snapshots)
	if err != nil {
		return nil, errors.Wrapf(err, "db.FetchModelObjects")
	}
	return snapshots, nil
}

func (self *SGuest) getGuestUsage(guestCount int) (SQuota, SRegionQuota, error) {
	usage := SQuota{}
	regionUsage := SRegionQuota{}
	usage.Count = guestCount
	usage.Cpu = int(self.VcpuCount) * guestCount
	usage.Memory = int(self.VmemSize * guestCount)
	diskSize := self.getDiskSize()
	if diskSize < 0 {
		return usage, regionUsage, httperrors.NewInternalServerError("fetch disk size failed")
	}
	usage.Storage = self.getDiskSize() * guestCount
	netCount, err := self.NetworkCount()
	if err != nil && errors.Cause(err) != sql.ErrNoRows {
		return usage, regionUsage, err
	}
	regionUsage.Port = netCount
	// regionUsage.Bw = self.getBandwidth(false)
	eip, err := self.GetEipOrPublicIp()
	if err != nil && errors.Cause(err) != sql.ErrNoRows {
		return usage, regionUsage, err
	}
	if eip != nil {
		regionUsage.Eip = 1
	}
	return usage, regionUsage, nil
}

func (self *SGuestManager) checkGuestImage(ctx context.Context, input *api.ServerCreateInput) error {
	if len(input.GuestImageID) == 0 {
		return nil
	}

	guestImageId := input.GuestImageID
	params := jsonutils.NewDict()
	params.Add(jsonutils.JSONTrue, "details")

	s := auth.GetAdminSession(ctx, options.Options.Region)
	ret, err := image.GuestImages.Get(s, guestImageId, params)
	if err != nil {
		return errors.Wrap(err, "get guest image from glance error")
	}

	images := &api.SImagesInGuest{}
	err = ret.Unmarshal(images)
	if err != nil {
		return errors.Wrap(err, "unmarshal guest image")
	}
	input.GuestImageID = images.Id

	log.Infof("usage guest image %s(%s)", images.Name, images.Id)

	if len(input.Disks) > 0 {
		input.Disks[0].ImageId = images.RootImage.Id
	} else {
		input.Disks = append(input.Disks,
			&api.DiskConfig{
				ImageId: images.RootImage.Id,
			},
		)
	}
	for i := range images.DataImages {
		if len(input.Disks) > i+1 {
			input.Disks[i+1].ImageId = images.DataImages[i].Id
		} else {
			input.Disks = append(input.Disks,
				&api.DiskConfig{
					ImageId: images.DataImages[i].Id,
				},
			)
		}
	}

	return nil
}

func (self *SGuest) GetDiskIndex(diskId string) int8 {
	guestDisks, _ := self.GetGuestDisks()
	for _, gd := range guestDisks {
		if gd.DiskId == diskId {
			return gd.Index
		}
	}
	return -1
}

func (guest *SGuest) GetRegionalQuotaKeys() (quotas.IQuotaKeys, error) {
	host, _ := guest.GetHost()
	if host == nil {
		return nil, errors.Wrap(httperrors.ErrInvalidStatus, "no valid host")
	}
	provider := host.GetCloudprovider()
	if provider == nil && len(host.ManagerId) > 0 {
		return nil, errors.Wrap(httperrors.ErrInvalidStatus, "no valid manager")
	}
	region, _ := host.GetRegion()
	if region == nil {
		return nil, errors.Wrap(httperrors.ErrInvalidStatus, "no valid region")
	}
	return fetchRegionalQuotaKeys(rbacscope.ScopeProject, guest.GetOwnerId(), region, provider), nil
}

func (guest *SGuest) GetCloudprovider() (*SCloudprovider, error) {
	hosts := HostManager.Query("manager_id").Equals("id", guest.HostId).SubQuery()
	q := CloudproviderManager.Query().In("id", hosts)
	ret := &SCloudprovider{}
	ret.SetModelManager(CloudproviderManager, ret)
	err := q.First(ret)
	if err != nil {
		return nil, errors.Wrapf(err, "q.First")
	}
	return ret, nil
}

func (guest *SGuest) GetQuotaKeys() (quotas.IQuotaKeys, error) {
	provider, _ := guest.GetCloudprovider()
	zone, _ := guest.GetZone()
	hypervisor := guest.Hypervisor
	return fetchComputeQuotaKeys(
		rbacscope.ScopeProject,
		guest.GetOwnerId(),
		zone,
		provider,
		hypervisor,
	), nil
}

func (guest *SGuest) GetUsages() []db.IUsage {
	if guest.PendingDeleted || guest.Deleted {
		return nil
	}
	usage, regionUsage, err := guest.getGuestUsage(1)
	if err != nil {
		log.Errorf("guest.getGuestUsage fail %s", err)
		return nil
	}
	keys, err := guest.GetQuotaKeys()
	if err != nil {
		log.Errorf("guest.GetQuotaKeys fail %s", err)
		return nil
	}
	usage.SetKeys(keys)
	regionUsage.SetKeys(keys.(SComputeResourceKeys).SRegionalCloudResourceKeys)
	return []db.IUsage{
		&usage,
		&regionUsage,
	}
}

var (
	// `^[a-zA-Z][a-zA-Z0-9._@-]*$`)
	serverNameREG = regexp.MustCompile(`^[a-zA-Z$][a-zA-Z0-9-${}.]*$`)
	hostnameREG   = regexp.MustCompile(`^[a-z$][a-z0-9-${}.]*$`)
)

func (manager *SGuestManager) ValidateNameLoginAccount(name string) error {
	if serverNameREG.MatchString(name) {
		return nil
	}
	return httperrors.NewInputParameterError("name starts with letter, and contains letter, number and - only")
}

func (guest *SGuest) StartRemoteUpdateTask(ctx context.Context, userCred mcclient.TokenCredential, replaceTags bool, parentTaskId string) error {
	data := jsonutils.NewDict()
	if replaceTags {
		data.Add(jsonutils.JSONTrue, "replace_tags")
	}
	if task, err := taskman.TaskManager.NewTask(ctx, "GuestRemoteUpdateTask", guest, userCred, data, parentTaskId, "", nil); err != nil {
		log.Errorln(err)
		return errors.Wrap(err, "Start GuestRemoteUpdateTask")
	} else {
		guest.SetStatus(ctx, userCred, api.VM_UPDATE_TAGS, "StartRemoteUpdateTask")
		task.ScheduleRun(nil)
	}
	return nil
}

func (guest *SGuest) OnMetadataUpdated(ctx context.Context, userCred mcclient.TokenCredential) {
	if len(guest.ExternalId) == 0 || options.Options.KeepTagLocalization {
		return
	}
	host, err := guest.GetHost()
	if err != nil {
		return
	}
	if account := host.GetCloudaccount(); account != nil && account.ReadOnly {
		return
	}
	err = guest.StartRemoteUpdateTask(ctx, userCred, true, "")
	if err != nil {
		log.Errorf("StartRemoteUpdateTask fail: %s", err)
	}
}

func (self *SGuest) GetAddress() (string, error) {
	gns, err := self.GetNetworks("")
	if err != nil {
		return "", errors.Wrapf(err, "GetNetworks")
	}
	for _, gn := range gns {
		if !gn.IsExit(nil) {
			return gn.IpAddr, nil
		}
	}
	return "", errors.Wrapf(cloudprovider.ErrNotFound, "guest %s address", self.Name)
}

func (guest *SGuest) InferPowerStates() {
	if len(guest.PowerStates) == 0 {
		switch guest.Status {
		case api.VM_READY:
			guest.PowerStates = api.VM_POWER_STATES_OFF
		case api.VM_UNKNOWN:
			guest.PowerStates = api.VM_POWER_STATES_UNKNOWN
		case api.VM_INIT:
			guest.PowerStates = api.VM_POWER_STATES_ON
		case api.VM_SCHEDULE:
			guest.PowerStates = api.VM_POWER_STATES_ON
		case api.VM_SCHEDULE_FAILED:
			guest.PowerStates = api.VM_POWER_STATES_ON
		case api.VM_CREATE_NETWORK:
			guest.PowerStates = api.VM_POWER_STATES_ON
		case api.VM_NETWORK_FAILED:
			guest.PowerStates = api.VM_POWER_STATES_ON
		case api.VM_DEVICE_FAILED:
			guest.PowerStates = api.VM_POWER_STATES_UNKNOWN
		case api.VM_CREATE_FAILED:
			guest.PowerStates = api.VM_POWER_STATES_OFF
		case api.VM_CREATE_DISK:
			guest.PowerStates = api.VM_POWER_STATES_OFF
		case api.VM_DISK_FAILED:
			guest.PowerStates = api.VM_POWER_STATES_OFF
		case api.VM_IMAGE_CACHING:
			guest.PowerStates = api.VM_POWER_STATES_ON
		case api.VM_START_DEPLOY:
			guest.PowerStates = api.VM_POWER_STATES_ON
		case api.VM_DEPLOYING:
			guest.PowerStates = api.VM_POWER_STATES_OFF
		case api.VM_START_START:
			guest.PowerStates = api.VM_POWER_STATES_ON
		case api.VM_STARTING:
			guest.PowerStates = api.VM_POWER_STATES_ON
		case api.VM_START_FAILED:
			guest.PowerStates = api.VM_POWER_STATES_OFF
		case api.VM_RUNNING:
			guest.PowerStates = api.VM_POWER_STATES_ON
		case api.VM_START_STOP:
			guest.PowerStates = api.VM_POWER_STATES_ON
		case api.VM_STOPPING:
			guest.PowerStates = api.VM_POWER_STATES_ON
		case api.VM_STOP_FAILED:
			guest.PowerStates = api.VM_POWER_STATES_ON
		case api.VM_RENEWING:
			guest.PowerStates = api.VM_POWER_STATES_ON
		case api.VM_RENEW_FAILED:
			guest.PowerStates = api.VM_POWER_STATES_ON
		case api.VM_ATTACH_DISK:
			guest.PowerStates = api.VM_POWER_STATES_UNKNOWN
		case api.VM_DETACH_DISK:
			guest.PowerStates = api.VM_POWER_STATES_UNKNOWN
		default:
			guest.PowerStates = api.VM_POWER_STATES_UNKNOWN
		}
	}
}

func (guest *SGuest) HasBackupGuest() bool {
	return guest.BackupHostId != ""
}

func (guest *SGuest) SetGuestBackupMirrorJobInProgress(ctx context.Context, userCred mcclient.TokenCredential) error {
	return guest.SetMetadata(ctx, api.MIRROR_JOB, api.MIRROR_JOB_INPROGRESS, userCred)
}

func (guest *SGuest) SetKickstartConfig(ctx context.Context, config *api.KickstartConfig, userCred mcclient.TokenCredential) error {
	if config == nil {
		return guest.RemoveMetadata(ctx, api.VM_METADATA_KICKSTART_CONFIG, userCred)
	}

	if err := validateKickstartConfig(config); err != nil {
		return errors.Wrap(err, "validate kickstart config")
	}

	configJson := jsonutils.Marshal(config)
	return guest.SetMetadata(ctx, api.VM_METADATA_KICKSTART_CONFIG, configJson, userCred)
}

func (guest *SGuest) GetKickstartConfig(ctx context.Context, userCred mcclient.TokenCredential) (*api.KickstartConfig, error) {
	configJson := guest.GetMetadataJson(ctx, api.VM_METADATA_KICKSTART_CONFIG, userCred)
	if configJson == nil {
		return nil, nil
	}

	config := &api.KickstartConfig{}
	if err := configJson.Unmarshal(config); err != nil {
		return nil, errors.Wrap(err, "unmarshal kickstart config")
	}

	return config, nil
}

func (guest *SGuest) SetKickstartStatus(ctx context.Context, status string, userCred mcclient.TokenCredential) error {
	if !utils.IsInStringArray(status, api.VM_KICKSTART_STATUS) {
		return errors.Errorf("invalid kickstart status: %s", status)
	}
	return guest.SetStatus(ctx, userCred, status, "")
}

func (guest *SGuest) GetKickstartStatus(ctx context.Context, userCred mcclient.TokenCredential) string {
	if utils.IsInStringArray(guest.Status, api.VM_KICKSTART_STATUS) {
		return guest.Status
	}
	return ""
}

func (guest *SGuest) IsInKickstartStatus() bool {
	return utils.IsInStringArray(guest.Status, api.VM_KICKSTART_STATUS)
}

func (guest *SGuest) SetKickstartType(ctx context.Context, kickstartType string, userCred mcclient.TokenCredential) error {
	if !utils.IsInStringArray(kickstartType, api.KICKSTART_VALID_TYPES) {
		return errors.Errorf("invalid kickstart type: %s", kickstartType)
	}
	return guest.SetMetadata(ctx, api.VM_METADATA_KICKSTART_TYPE, kickstartType, userCred)
}

func (guest *SGuest) GetKickstartType(ctx context.Context, userCred mcclient.TokenCredential) string {
	kickstartType := guest.GetMetadata(ctx, api.VM_METADATA_KICKSTART_TYPE, userCred)
	if kickstartType == "" {
		return api.KICKSTART_TYPE_URL
	}
	return kickstartType
}

func (guest *SGuest) IsKickstartEnabled(ctx context.Context, userCred mcclient.TokenCredential) bool {
	config, err := guest.GetKickstartConfig(ctx, userCred)
	if err != nil || config == nil {
		return false
	}

	if config.Enabled == nil {
		return true
	}

	return *config.Enabled
}

func (guest *SGuest) SetGuestBackupMirrorJobNotReady(ctx context.Context, userCred mcclient.TokenCredential) error {
	return guest.SetMetadata(ctx, api.MIRROR_JOB, "", userCred)
}

func (guest *SGuest) TrySetGuestBackupMirrorJobReady(ctx context.Context, userCred mcclient.TokenCredential) error {
	if guest.IsGuestBackupMirrorJobFailed(ctx, userCred) {
		// can't update guest backup mirror job status from failed to ready
		return nil
	}
	return guest.SetMetadata(ctx, api.MIRROR_JOB, api.MIRROR_JOB_READY, userCred)
}

func (guest *SGuest) SetGuestBackupMirrorJobFailed(ctx context.Context, userCred mcclient.TokenCredential) error {
	return guest.SetMetadata(ctx, api.MIRROR_JOB, api.MIRROR_JOB_FAILED, userCred)
}

func (guest *SGuest) IsGuestBackupMirrorJobFailed(ctx context.Context, userCred mcclient.TokenCredential) bool {
	return guest.GetMetadata(ctx, api.MIRROR_JOB, userCred) == api.MIRROR_JOB_FAILED
}

func (guest *SGuest) IsGuestBackupMirrorJobReady(ctx context.Context, userCred mcclient.TokenCredential) bool {
	return guest.GetMetadata(ctx, api.MIRROR_JOB, userCred) == api.MIRROR_JOB_READY
}

func (guest *SGuest) GetGuestBackupMirrorJobStatus(ctx context.Context, userCred mcclient.TokenCredential) string {
	return guest.GetMetadata(ctx, api.MIRROR_JOB, userCred)
}

func (guest *SGuest) ResetGuestQuorumChildIndex(ctx context.Context, userCred mcclient.TokenCredential) error {
	return guest.SetMetadata(ctx, api.QUORUM_CHILD_INDEX, "", userCred)
}

type SGuestTotalCount struct {
	apis.TotalCountBase
	CpuCount  int
	MemMb     int
	DiskMb    int64
	DiskCount int
}

func (manager *SGuestManager) CustomizedTotalCount(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, totalQ *sqlchemy.SQuery) (int, jsonutils.JSONObject, error) {
	results := SGuestTotalCount{}

	totalQ = totalQ.AppendField(sqlchemy.SUM("cpu_count", totalQ.Field("vcpu_count")))
	totalQ = totalQ.AppendField(sqlchemy.SUM("mem_mb", totalQ.Field("vmem_size")))

	err := totalQ.First(&results)
	if err != nil {
		return -1, nil, errors.Wrap(err, "SGuestManager query total")
	}

	log.Debugf("CustomizedTotalCount %s", jsonutils.Marshal(results))

	diskQ := DiskManager.Query()
	diskGuestQ := GuestdiskManager.Query().SubQuery()
	diskQ = diskQ.Join(diskGuestQ, sqlchemy.Equals(diskQ.Field("id"), diskGuestQ.Field("disk_id")))
	totalSQ := totalQ.ResetFields().SubQuery()
	diskQ = diskQ.Join(totalSQ, sqlchemy.Equals(diskGuestQ.Field("guest_id"), totalSQ.Field("id")))
	diskQ = diskQ.AppendField(sqlchemy.COUNT("disk_count"))
	diskQ = diskQ.AppendField(sqlchemy.SUM("disk_mb", diskQ.Field("disk_size")))

	err = diskQ.First(&results)
	if err != nil {
		return -1, nil, errors.Wrap(err, "SGuestManager query total_disk")
	}

	_, statusInfo, err := manager.SVirtualResourceBaseManager.CustomizedTotalCount(ctx, userCred, query, totalQ)
	if err != nil {
		return -1, nil, errors.Wrapf(err, "virt.CustomizedTotalCount")
	}

	ret := jsonutils.Marshal(results).(*jsonutils.JSONDict)
	ret.Update(statusInfo)

	return results.Count, ret, nil
}

func (guest *SGuest) IsSriov() bool {
	nics, err := guest.GetNetworks("")
	if err != nil {
		log.Errorf("guest.GetNetworks fail %s", err)
		return false
	}
	for i := range nics {
		if nics[i].Driver == api.NETWORK_DRIVER_VFIO {
			return true
		}
	}
	return false
}

func (guest *SGuest) getDisksCandidateHostIds() ([]string, error) {
	disks, err := guest.GetDisks()
	if err != nil {
		return nil, errors.Wrap(err, "guest.GetDisks")
	}
	ret := stringutils2.NewSortedStrings(nil)
	for i := range disks {
		candidates, err := disks[i].getCandidateHostIds()
		if err != nil {
			return nil, errors.Wrap(err, "getCandidateHostIds")
		}
		sorted := stringutils2.NewSortedStrings(candidates)
		if i > 0 {
			ret = stringutils2.Intersect(ret, sorted)
		} else {
			ret = sorted
		}
	}
	return ret, nil
}

func (guest *SGuest) SaveLastStartAt() error {
	_, err := db.Update(guest, func() error {
		guest.LastStartAt = time.Now().UTC()
		return nil
	})
	return errors.Wrap(err, "SaveLastStartAt")
}

func (guest *SGuest) finalizeFakeDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, task taskman.ITask) {
	db.OpsLog.LogEvent(guest, db.ACT_PENDING_DELETE, guest.GetShortDesc(ctx), userCred)
	logclient.AddActionLogWithStartable(task, guest, logclient.ACT_PENDING_DELETE, guest.GetShortDesc(ctx), userCred, true)
	if !guest.IsSystem {
		guest.EventNotify(ctx, userCred, notifyclient.ActionPendingDelete)
	}
}

func (guest *SGuest) finalizeRealDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, task taskman.ITask) {
	guest.RealDelete(ctx, userCred)
	guest.RemoveAllMetadata(ctx, userCred)
	db.OpsLog.LogEvent(guest, db.ACT_DELOCATE, guest.GetShortDesc(ctx), userCred)
	logclient.AddActionLogWithStartable(task, guest, logclient.ACT_DELOCATE, nil, userCred, true)
	if !guest.IsSystem {
		guest.EventNotify(ctx, userCred, notifyclient.ActionDelete)
	}
	HostManager.ClearSchedDescCache(guest.HostId)
}

func (guest *SGuest) FinalizeDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, task taskman.ITask, data jsonutils.JSONObject) {
	if jsonutils.QueryBoolean(data, "real_delete", false) {
		guest.finalizeRealDeleteTask(ctx, userCred, task)
	} else {
		guest.finalizeFakeDeleteTask(ctx, userCred, task)
	}
}

func (guest *SGuest) StartBaseDeleteTask(ctx context.Context, t taskman.ITask) error {
	task, err := taskman.TaskManager.NewTask(ctx, "BaseGuestDeleteTask", guest, t.GetUserCred(), t.GetParams(), t.GetTaskId(), "", nil)
	if err != nil {
		return errors.Wrap(err, "StartBaseDeleteTask")
	}
	err = task.ScheduleRun(nil)
	if err != nil {
		return errors.Wrap(err, "ScheduleRun")
	}
	return nil
}
