mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/yunionio/cloudpods.git
synced 2026-09-20 08:03:53 +08:00
7835 lines
236 KiB
Go
7835 lines
236 KiB
Go
// 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("check guest disk count failed %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 {
|
||
guestIdev := GuestIsolatedDeviceManager.Query().SubQuery()
|
||
sgq := guestIdev.Query(guestIdev.Field("guest_id")).GroupBy(guestIdev.Field("guest_id"))
|
||
|
||
if len(dType) > 0 {
|
||
isodev := IsolatedDeviceManager.Query().SubQuery()
|
||
sgq = sgq.Join(isodev, sqlchemy.Equals(guestIdev.Field("isolated_device_id"), isodev.Field("id")))
|
||
sgq = sgq.Filter(sqlchemy.Startswith(isodev.Field("dev_type"), dType))
|
||
}
|
||
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.IsolatedDeviceId) > 0 {
|
||
sq := GuestIsolatedDeviceManager.Query("guest_id").
|
||
Equals("isolated_device_id", query.IsolatedDeviceId).SubQuery()
|
||
q = q.In("id", sq)
|
||
}
|
||
|
||
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")
|
||
}
|
||
if err := manager.initCloudpodsGuest(); err != nil {
|
||
return errors.Wrapf(err, "initCloudpodsGuest")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (manager *SGuestManager) initCloudpodsGuest() error {
|
||
q := manager.Query().Equals("hypervisor", "cloudpods")
|
||
guests := []SGuest{}
|
||
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 {
|
||
guests[i].Hypervisor = api.HYPERVISOR_DEFAULT
|
||
return nil
|
||
})
|
||
}
|
||
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 allowed 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) {
|
||
zone, provider, brand, err := resolveServerCreateQuotaScope(input)
|
||
if err != nil {
|
||
return SComputeResourceKeys{}, err
|
||
}
|
||
|
||
hypervisor := input.Hypervisor
|
||
if len(hypervisor) == 0 {
|
||
hypervisor = api.HYPERVISOR_KVM
|
||
}
|
||
providerStr := input.Provider
|
||
if len(providerStr) == 0 {
|
||
providerStr = brand
|
||
}
|
||
|
||
driver, err := GetDriver(hypervisor, providerStr)
|
||
if err != nil {
|
||
return SComputeResourceKeys{}, err
|
||
}
|
||
keys := driver.GetComputeQuotaKeys(rbacscope.ScopeProject, ownerId, brand)
|
||
|
||
if provider != nil {
|
||
account, _ := provider.GetCloudaccount()
|
||
if account != nil {
|
||
keys.Provider = account.Provider
|
||
keys.Brand = account.Brand
|
||
keys.CloudEnv = account.GetCloudEnv()
|
||
keys.AccountId = account.Id
|
||
}
|
||
keys.ManagerId = provider.Id
|
||
}
|
||
if zone != nil {
|
||
keys.ZoneId = zone.Id
|
||
keys.RegionId = zone.CloudregionId
|
||
} else if len(input.PreferRegion) > 0 {
|
||
keys.RegionId = input.PreferRegion
|
||
}
|
||
|
||
return keys, nil
|
||
}
|
||
|
||
func resolveServerCreateQuotaScope(input api.ServerCreateInput) (*SZone, *SCloudprovider, string, error) {
|
||
var zone *SZone
|
||
var provider *SCloudprovider
|
||
brand := ""
|
||
|
||
if len(input.PreferHost) > 0 {
|
||
hostObj, err := HostManager.FetchById(input.PreferHost)
|
||
if err != nil {
|
||
return nil, nil, "", err
|
||
}
|
||
host := hostObj.(*SHost)
|
||
if len(host.ZoneId) > 0 {
|
||
zoneObj, err := ZoneManager.FetchById(host.ZoneId)
|
||
if err != nil {
|
||
return nil, nil, "", err
|
||
}
|
||
zone = zoneObj.(*SZone)
|
||
}
|
||
provider = host.GetCloudprovider()
|
||
}
|
||
if zone == nil && len(input.PreferWire) > 0 {
|
||
wireObj, err := WireManager.FetchById(input.PreferWire)
|
||
if err != nil {
|
||
return nil, nil, "", err
|
||
}
|
||
wire := wireObj.(*SWire)
|
||
if len(wire.ZoneId) > 0 {
|
||
zoneObj, err := ZoneManager.FetchById(wire.ZoneId)
|
||
if err != nil {
|
||
return nil, nil, "", err
|
||
}
|
||
zone = zoneObj.(*SZone)
|
||
}
|
||
}
|
||
preferZone := input.PreferZone
|
||
if len(input.PreferZones) > 0 {
|
||
preferZone = input.PreferZones[0]
|
||
}
|
||
if zone == nil && len(preferZone) > 0 {
|
||
zoneObj, err := ZoneManager.FetchById(preferZone)
|
||
if err != nil {
|
||
return nil, nil, "", err
|
||
}
|
||
zone = zoneObj.(*SZone)
|
||
}
|
||
if provider == nil && len(input.PreferManager) > 0 {
|
||
managerObj, err := CloudproviderManager.FetchById(input.PreferManager)
|
||
if err != nil {
|
||
return nil, nil, "", err
|
||
}
|
||
provider = managerObj.(*SCloudprovider)
|
||
}
|
||
if zone != nil {
|
||
region, err := zone.GetRegion()
|
||
if err != nil {
|
||
return nil, nil, "", errors.Wrapf(err, "GetRegion")
|
||
}
|
||
brand = region.Provider
|
||
} else if len(input.PreferRegion) > 0 {
|
||
regionObj, err := CloudregionManager.FetchById(input.PreferRegion)
|
||
if err != nil {
|
||
return nil, nil, "", err
|
||
}
|
||
brand = regionObj.(*SCloudregion).Provider
|
||
}
|
||
|
||
return zone, provider, brand, 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("instance snapshot %s not found", input.InstanceSnapshotId)
|
||
}
|
||
if err != nil {
|
||
return nil, httperrors.NewInternalServerError("fetch instance snapshot failed %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 err != nil {
|
||
return nil, errors.Wrap(err, "ToInstanceCreateInput")
|
||
}
|
||
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("instance backup %s not found", input.InstanceBackupId)
|
||
}
|
||
if err != nil {
|
||
return nil, httperrors.NewInternalServerError("fetch instance backup failed %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 err != nil {
|
||
return nil, errors.Wrap(err, "ToInstanceCreateInput")
|
||
}
|
||
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("metadata must be less than 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 exceeds 32 characters")
|
||
}
|
||
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")
|
||
}
|
||
|
||
preferZones := input.GetPreferZones()
|
||
if len(preferZones) > 0 && len(input.Provider) == 0 {
|
||
zoneObj, err := ZoneManager.FetchById(preferZones[0])
|
||
if err != nil {
|
||
return nil, errors.Wrapf(err, "zone fetch by id %s", preferZones[0])
|
||
}
|
||
zone := zoneObj.(*SZone)
|
||
input.PreferRegion = zone.CloudregionId
|
||
} else 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
|
||
}
|
||
// pve, vmware provider is OneCloud
|
||
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.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 images; consider using the 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 failed %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 != "" && 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:
|
||
supportUEFI := imgSupportUEFI != nil && *imgSupportUEFI
|
||
supportBIOS := imgSupportBIOS != nil && *imgSupportBIOS
|
||
if input.Hypervisor == api.HYPERVISOR_BAREMETAL && supportUEFI && supportBIOS {
|
||
// decided by selected host
|
||
input.Bios = ""
|
||
} else {
|
||
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"
|
||
imageMachineType := imgProperties[imageapi.IMAGE_MACHINE_TYPE]
|
||
if len(input.Machine) == 0 && len(imageMachineType) > 0 {
|
||
input.Machine = imageMachineType
|
||
}
|
||
imgIsWindows := imgProperties[imageapi.IMAGE_OS_TYPE] == "Windows"
|
||
|
||
hasGpuVga := func() bool {
|
||
for i := 0; i < len(input.IsolatedDevices); i++ {
|
||
if input.IsolatedDevices[i].GpuType == api.GPU_VGA {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}()
|
||
if imgIsWindows && hasGpuVga && input.Bios != "UEFI" {
|
||
return nil, httperrors.NewInputParameterError("Windows with GPU VGA requires a 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_PROXMOX, 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("missing operating system information")
|
||
}
|
||
|
||
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 does not specify attached disk size", sku.Name)
|
||
}
|
||
if len(sku.AttachedDiskType) == 0 {
|
||
return nil, httperrors.NewInputParameterError("sku %s does not specify 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 cannot be assigned 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 failed %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 failed %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 hypervisor %s does not support postpaid expiration", 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 failed %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 failed %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)
|
||
}
|
||
|
||
// default: both check on
|
||
// switch: mac check off, also implies ip check off
|
||
// router: mac check on, ip check off
|
||
if input.SrcMacCheck != nil && !*input.SrcMacCheck {
|
||
srcIpCheck := false
|
||
input.SrcIpCheck = &srcIpCheck
|
||
}
|
||
|
||
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 failed %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].SharingMode == api.DEVICE_SHARING_MODE_MDEV {
|
||
nvidiaVgpuCnt += 1
|
||
} else if input.IsolatedDevices[i].DevType == api.GPU_TYPE {
|
||
gpuCnt += 1
|
||
}
|
||
}
|
||
|
||
if nvidiaVgpuCnt > 1 {
|
||
return nil, httperrors.NewBadRequestError("Nvidia vGPU count cannot exceed 1")
|
||
}
|
||
if nvidiaVgpuCnt > 0 && gpuCnt > 0 {
|
||
return nil, httperrors.NewBadRequestError("Nvidia vGPU cannot 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()
|
||
}
|
||
|
||
if len(input.NetworkTags) > 0 {
|
||
if hypervisor != api.HYPERVISOR_GOOGLE {
|
||
return nil, httperrors.NewInputParameterError("network_tags is only supported by %s", api.HYPERVISOR_GOOGLE)
|
||
}
|
||
tags := make([]string, 0, len(input.NetworkTags))
|
||
for _, tag := range input.NetworkTags {
|
||
tag = strings.TrimSpace(tag)
|
||
if len(tag) == 0 {
|
||
continue
|
||
}
|
||
if !utils.IsInStringArray(tag, tags) {
|
||
tags = append(tags, tag)
|
||
}
|
||
}
|
||
input.NetworkTags = tags
|
||
}
|
||
|
||
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 if hypervisor == api.HYPERVISOR_GOOGLE && len(input.NetworkTags) > 0 {
|
||
// GCP 传入网络标记时可跳过安全组
|
||
input.SecgroupId = ""
|
||
input.Secgroups = []string{}
|
||
} else {
|
||
input.SecgroupId = options.Options.GetDefaultSecurityGroupId(hypervisor)
|
||
}
|
||
|
||
maxSecgrpCount := driver.GetMaxSecurityGroupCount()
|
||
if maxSecgrpCount == 0 { //esxi 不支持安全组
|
||
input.SecgroupId = ""
|
||
input.Secgroups = []string{}
|
||
} else if len(input.SecgroupId) > 0 && len(input.Secgroups)+1 > maxSecgrpCount {
|
||
return nil, httperrors.NewInputParameterError("%s can 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(®ionDefinitions)
|
||
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 requires cloudregion in %s", definitions[i].Name, definitions[i].Parameters)
|
||
}
|
||
case api.POLICY_DEFINITION_CONDITION_NOT_IN:
|
||
if isIn {
|
||
return httperrors.NewPolicyDefinitionError("policy definition %s requires 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 must contain tag %s", definitions[i].Name, tag)
|
||
}
|
||
case api.POLICY_DEFINITION_CONDITION_EXCEPT:
|
||
if isIn {
|
||
return httperrors.NewPolicyDefinitionError("policy definition %s excludes 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.NewInputParameterError("os_type is required")
|
||
}
|
||
|
||
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 is 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
|
||
if len(eipStr) > 0 || input.EipTxBw > 0 || input.EipRxBw > 0 || input.EipBw > 0 {
|
||
if !driver.IsSupportEip() {
|
||
return httperrors.NewNotImplementedError("EIP is 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())
|
||
}
|
||
}
|
||
if data.Contains("name") && len(self.ExternalId) == 0 && len(self.HostId) > 0 {
|
||
if err := self.StartSyncTask(ctx, userCred, false, ""); err != nil {
|
||
log.Errorf("StartSyncTask after rename fail: %s", err)
|
||
}
|
||
}
|
||
}
|
||
|
||
func (manager *SGuestManager) checkCreateQuota(
|
||
ctx context.Context,
|
||
userCred mcclient.TokenCredential,
|
||
ownerId mcclient.IIdentityProvider,
|
||
input api.ServerCreateInput,
|
||
hasBackup bool,
|
||
count int,
|
||
) error {
|
||
req, regionReq, err := getGuestResourceRequirements(ctx, userCred, input, ownerId, count, hasBackup)
|
||
if err != nil {
|
||
return errors.Wrap(err, "getGuestResourceRequirements")
|
||
}
|
||
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, ®ionReq)
|
||
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, error) {
|
||
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
|
||
eipTxBw := input.EipTxBw
|
||
eipRxBw := input.EipRxBw
|
||
if eipBw > 0 || eipTxBw > 0 || eipRxBw > 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, err := serverCreateInput2ComputeQuotaKeys(input, ownerId)
|
||
if err != nil {
|
||
return SQuota{}, SRegionQuota{}, errors.Wrap(err, "serverCreateInput2ComputeQuotaKeys")
|
||
}
|
||
req.SetKeys(keys)
|
||
regionReq.SetKeys(keys.SRegionalCloudResourceKeys)
|
||
return req, regionReq, nil
|
||
}
|
||
|
||
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, err := getGuestResourceRequirements(ctx, userCred, input, ownerId, len(items), input.Backup)
|
||
if err != nil {
|
||
for i := range items {
|
||
guest := items[i].(*SGuest)
|
||
guest.SetStatus(ctx, userCred, api.VM_CREATE_FAILED, err.Error())
|
||
}
|
||
return
|
||
}
|
||
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.Strings()...)
|
||
}
|
||
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.Strings()...)
|
||
}
|
||
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
|
||
RxBwLimit int
|
||
TxBwLimit 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,
|
||
rxBwLimit: args.RxBwLimit,
|
||
txBwLimit: args.TxBwLimit,
|
||
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
|
||
rxBwLimit int
|
||
txBwLimit 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,
|
||
rxBwLimit: args.rxBwLimit,
|
||
txBwLimit: args.txBwLimit,
|
||
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) {
|
||
findByIp := func() (*SNetwork, error) {
|
||
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())
|
||
}
|
||
}
|
||
return host.getNetworkOfIPOnHost(ctx, ip)
|
||
}
|
||
|
||
// Proxmox does not sync remote L2/networks; always resolve on-premise network by IP
|
||
if host.HostType == api.HOST_TYPE_PROXMOX {
|
||
return findByIp()
|
||
}
|
||
|
||
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)
|
||
}
|
||
return findByIp()
|
||
}
|
||
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()
|
||
|
||
guestIdevs := GuestIsolatedDeviceManager.Query().SubQuery()
|
||
|
||
isoDevQuery := guestIdevs.Query(guestIdevs.Field("guest_id"), sqlchemy.COUNT("device_sum"))
|
||
isoDevQuery = isoDevQuery.GroupBy(guestIdevs.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")
|
||
}
|
||
gdev, err := self.GetGuestIsolatedDeviceByNetworkIndex(gn.Index)
|
||
if err != nil {
|
||
return errors.Wrap(err, "self.GetIsolatedDeviceByNetworkIndex")
|
||
}
|
||
dev := gdev.GetIsolatedDevice()
|
||
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.FindAvailableNicWiresByModel(netConfig.SriovDevice.Model)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "FindAvailableNicWiresByModel")
|
||
}
|
||
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,
|
||
RxBwLimit: netConfig.RxBwLimit,
|
||
TxBwLimit: netConfig.TxBwLimit,
|
||
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)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
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) {
|
||
gq := GuestIsolatedDeviceManager.Query().Equals("guest_id", self.Id).SubQuery()
|
||
q := IsolatedDeviceManager.Query()
|
||
q = q.Join(gq, sqlchemy.Equals(q.Field("id"), gq.Field("isolated_device_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) GetGuestIsolatedDeviceByNetworkIndex(index int) (*SGuestIsolatedDevice, error) {
|
||
dev := SGuestIsolatedDevice{}
|
||
q := GuestIsolatedDeviceManager.Query().Equals("network_index", index).Equals("guest_id", self.Id)
|
||
|
||
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(GuestIsolatedDeviceManager, &dev)
|
||
return &dev, nil
|
||
}
|
||
|
||
func (self *SGuest) GetIsolatedDeviceByDiskIndex(index int8) (*SIsolatedDevice, error) {
|
||
dev := SIsolatedDevice{}
|
||
q := IsolatedDeviceManager.Query()
|
||
gidq := GuestIsolatedDeviceManager.Query().
|
||
Equals("guest_id", self.Id).Equals("disk_index", index).SubQuery()
|
||
q = q.Join(gidq, sqlchemy.Equals(q.Field("id"), gidq.Field("isolated_device_id")))
|
||
|
||
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
|
||
}
|
||
|
||
isolatedDevs, _ := self.GetGuestIsolatedDevices()
|
||
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.GetGuestIsolatedDevices()
|
||
gpuSpecs := []GpuSpec{}
|
||
for i := range guestgpus {
|
||
guestgpu := guestgpus[i].GetIsolatedDevice()
|
||
if guestgpu.DevType == api.GPU_TYPE {
|
||
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 associated 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.EipTxBw = genInput.EipTxBw
|
||
userInput.EipRxBw = genInput.EipRxBw
|
||
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.EipTxBw = eip.TxBwLimit
|
||
r.EipRxBw = eip.RxBwLimit
|
||
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.RxBwLimit = guestNetwork.RxBwLimit
|
||
netConf.TxBwLimit = guestNetwork.TxBwLimit
|
||
netConf.RequireTeaming = requireTeaming
|
||
// netConf.NetType
|
||
ret = append(ret, netConf)
|
||
}
|
||
return ret
|
||
}
|
||
|
||
func (self *SGuest) ToIsolatedDevicesConfig() []*api.IsolatedDeviceConfig {
|
||
guestIsolatedDevices, _ := self.GetGuestIsolatedDevices()
|
||
if len(guestIsolatedDevices) == 0 {
|
||
return nil
|
||
}
|
||
ret := make([]*api.IsolatedDeviceConfig, len(guestIsolatedDevices))
|
||
for idx := range guestIsolatedDevices {
|
||
dev := guestIsolatedDevices[idx].GetIsolatedDevice()
|
||
devConf := new(api.IsolatedDeviceConfig)
|
||
devConf.Model = dev.Model
|
||
devConf.Vendor = dev.getVendor()
|
||
devConf.DevType = dev.DevType
|
||
devConf.SharingMode = dev.SharingMode
|
||
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,
|
||
®ionUsage,
|
||
}
|
||
}
|
||
|
||
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
|
||
}
|