mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-10-25 10:16:12 +08:00
The glance service hangs and other external reasons may cause the guest template to be judged as invalid. Therefore, health inspection should be performed on time for invalid guest templates.
821 lines
28 KiB
Go
821 lines
28 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 (
|
||
"context"
|
||
"fmt"
|
||
"time"
|
||
|
||
"yunion.io/x/jsonutils"
|
||
"yunion.io/x/log"
|
||
"yunion.io/x/pkg/errors"
|
||
"yunion.io/x/pkg/util/sets"
|
||
"yunion.io/x/pkg/utils"
|
||
"yunion.io/x/sqlchemy"
|
||
|
||
"yunion.io/x/onecloud/pkg/apis"
|
||
computeapis "yunion.io/x/onecloud/pkg/apis/compute"
|
||
"yunion.io/x/onecloud/pkg/cloudcommon/cmdline"
|
||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||
"yunion.io/x/onecloud/pkg/compute/options"
|
||
"yunion.io/x/onecloud/pkg/httperrors"
|
||
"yunion.io/x/onecloud/pkg/mcclient"
|
||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||
"yunion.io/x/onecloud/pkg/mcclient/modules"
|
||
"yunion.io/x/onecloud/pkg/util/logclient"
|
||
"yunion.io/x/onecloud/pkg/util/rbacutils"
|
||
"yunion.io/x/onecloud/pkg/util/stringutils2"
|
||
)
|
||
|
||
const (
|
||
IMAGE_TYPE_NORMAL = "normal"
|
||
IMAGE_TYPE_GUEST = "guest"
|
||
)
|
||
|
||
type SGuestTemplateManager struct {
|
||
db.SSharableVirtualResourceBaseManager
|
||
SCloudregionResourceBaseManager
|
||
SVpcResourceBaseManager
|
||
}
|
||
|
||
type SGuestTemplate struct {
|
||
db.SSharableVirtualResourceBase
|
||
SCloudregionResourceBase
|
||
SVpcResourceBase
|
||
|
||
// 虚拟机CPU数量
|
||
VcpuCount int `nullable:"false" default:"1" create:"optional" json:"vcpu_count"`
|
||
|
||
// 虚拟机内存大小(MB)
|
||
VmemSize int `nullable:"false" create:"optional" json:"vmem_size"`
|
||
|
||
// 虚拟机操作系统类型
|
||
// pattern:Linux|Windows|VMWare
|
||
OsType string `width:"36" charset:"ascii" nullable:"true" create:"optional" json:"os_type" list:"user" get:"user"`
|
||
|
||
// 镜像类型
|
||
ImageType string `width:"10" charset:"ascii" nullabel:"true" default:"normal" create:"optional" json:"image_type"`
|
||
|
||
// 镜像ID
|
||
ImageId string `width:"128" charset:"ascii" create:"optional" json:"image_id"`
|
||
|
||
// 虚拟机技术
|
||
Hypervisor string `width:"16" charset:"ascii" default:"kvm" create:"optional" json:"hypervisor"`
|
||
|
||
// 计费方式
|
||
BillingType string `width:"16" charset:"ascii" default:"postpaid" create:"optional" list:"user" get:"user" json:"billing_type"`
|
||
|
||
// 其他配置信息
|
||
Content jsonutils.JSONObject `nullable:"false" list:"user" update:"user" create:"optional" json:"content"`
|
||
|
||
LastCheckTime time.Time
|
||
}
|
||
|
||
var GuestTemplateManager *SGuestTemplateManager
|
||
|
||
func init() {
|
||
GuestTemplateManager = &SGuestTemplateManager{
|
||
SSharableVirtualResourceBaseManager: db.NewSharableVirtualResourceBaseManager(
|
||
SGuestTemplate{},
|
||
"guesttemplates_tbl",
|
||
"servertemplate",
|
||
"servertemplates",
|
||
),
|
||
}
|
||
|
||
GuestTemplateManager.SetVirtualObject(GuestTemplateManager)
|
||
}
|
||
|
||
func (gtm *SGuestTemplateManager) ValidateCreateData(
|
||
ctx context.Context,
|
||
userCred mcclient.TokenCredential,
|
||
ownerId mcclient.IIdentityProvider,
|
||
query jsonutils.JSONObject,
|
||
input computeapis.GuestTemplateCreateInput,
|
||
) (computeapis.GuestTemplateCreateInput, error) {
|
||
|
||
var err error
|
||
if input.Content == nil {
|
||
return input, httperrors.NewMissingParameterError("content")
|
||
}
|
||
|
||
input.GuestTemplateInput, err = gtm.validateData(ctx, userCred, ownerId, query, input.GuestTemplateInput)
|
||
if err != nil {
|
||
return input, errors.Wrap(err, "gtm.validateData")
|
||
}
|
||
|
||
input.SharableVirtualResourceCreateInput, err = gtm.SSharableVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.SharableVirtualResourceCreateInput)
|
||
if err != nil {
|
||
return input, errors.Wrap(err, "SSharableVirtualResourceBaseManager.ValidateCreateData")
|
||
}
|
||
|
||
return input, nil
|
||
}
|
||
|
||
func (gt *SGuestTemplate) PostCreate(ctx context.Context, userCred mcclient.TokenCredential,
|
||
ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
||
gt.SetStatus(userCred, computeapis.GT_READY, "")
|
||
gt.updateCheckTime()
|
||
logclient.AddActionLogWithContext(ctx, gt, logclient.ACT_CREATE, nil, userCred, true)
|
||
}
|
||
|
||
func (gt *SGuestTemplate) updateCheckTime() error {
|
||
_, err := db.Update(gt, func() error {
|
||
gt.LastCheckTime = time.Now()
|
||
return nil
|
||
})
|
||
return err
|
||
}
|
||
|
||
func (gt *SGuestTemplate) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential,
|
||
query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
||
logclient.AddActionLogWithContext(ctx, gt, logclient.ACT_UPDATE, nil, userCred, true)
|
||
}
|
||
|
||
var HypervisorBrandMap = map[string]string{
|
||
computeapis.HYPERVISOR_KVM: computeapis.CLOUD_PROVIDER_ONECLOUD,
|
||
computeapis.HYPERVISOR_ESXI: computeapis.CLOUD_PROVIDER_VMWARE,
|
||
computeapis.HYPERVISOR_ALIYUN: computeapis.CLOUD_PROVIDER_ALIYUN,
|
||
computeapis.HYPERVISOR_QCLOUD: computeapis.CLOUD_PROVIDER_QCLOUD,
|
||
computeapis.HYPERVISOR_AZURE: computeapis.CLOUD_PROVIDER_AZURE,
|
||
computeapis.HYPERVISOR_AWS: computeapis.CLOUD_PROVIDER_AWS,
|
||
computeapis.HYPERVISOR_HUAWEI: computeapis.CLOUD_PROVIDER_HUAWEI,
|
||
computeapis.HYPERVISOR_OPENSTACK: computeapis.CLOUD_PROVIDER_OPENSTACK,
|
||
computeapis.HYPERVISOR_UCLOUD: computeapis.CLOUD_PROVIDER_UCLOUD,
|
||
computeapis.HYPERVISOR_ZSTACK: computeapis.CLOUD_PROVIDER_ZSTACK,
|
||
computeapis.HYPERVISOR_GOOGLE: computeapis.CLOUD_PROVIDER_GOOGLE,
|
||
computeapis.HYPERVISOR_CTYUN: computeapis.CLOUD_PROVIDER_CTYUN,
|
||
}
|
||
|
||
var BrandHypervisorMap = map[string]string{
|
||
computeapis.CLOUD_PROVIDER_ONECLOUD: computeapis.HYPERVISOR_KVM,
|
||
computeapis.CLOUD_PROVIDER_VMWARE: computeapis.HYPERVISOR_ESXI,
|
||
computeapis.CLOUD_PROVIDER_ALIYUN: computeapis.HYPERVISOR_ALIYUN,
|
||
computeapis.CLOUD_PROVIDER_QCLOUD: computeapis.HYPERVISOR_QCLOUD,
|
||
computeapis.CLOUD_PROVIDER_AZURE: computeapis.HYPERVISOR_AZURE,
|
||
computeapis.CLOUD_PROVIDER_AWS: computeapis.HYPERVISOR_AWS,
|
||
computeapis.CLOUD_PROVIDER_HUAWEI: computeapis.HYPERVISOR_HUAWEI,
|
||
computeapis.CLOUD_PROVIDER_OPENSTACK: computeapis.HYPERVISOR_OPENSTACK,
|
||
computeapis.CLOUD_PROVIDER_UCLOUD: computeapis.HYPERVISOR_UCLOUD,
|
||
computeapis.CLOUD_PROVIDER_ZSTACK: computeapis.HYPERVISOR_ZSTACK,
|
||
computeapis.CLOUD_PROVIDER_GOOGLE: computeapis.HYPERVISOR_GOOGLE,
|
||
computeapis.CLOUD_PROVIDER_CTYUN: computeapis.HYPERVISOR_CTYUN,
|
||
}
|
||
|
||
func Hypervisor2Brand(hypervisor string) string {
|
||
brand, ok := HypervisorBrandMap[hypervisor]
|
||
if !ok {
|
||
return "unkown"
|
||
}
|
||
return brand
|
||
}
|
||
|
||
func Brand2Hypervisor(brand string) string {
|
||
hypervisor, ok := BrandHypervisorMap[brand]
|
||
if !ok {
|
||
return "unkown"
|
||
}
|
||
return hypervisor
|
||
}
|
||
|
||
func (gtm *SGuestTemplateManager) validateContent(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, content *jsonutils.JSONDict) (*computeapis.ServerCreateInput, error) {
|
||
input, err := GuestManager.validateCreateData(ctx, userCred, ownerId, query, content)
|
||
if err != nil {
|
||
return nil, httperrors.NewInputParameterError("%v", err)
|
||
}
|
||
// check Image
|
||
imageId := input.Disks[0].ImageId
|
||
image, err := CachedimageManager.getImageInfo(ctx, userCred, imageId, true)
|
||
if err != nil {
|
||
return nil, errors.Wrapf(err, "getImageInfo of '%s'", imageId)
|
||
}
|
||
if image == nil {
|
||
return nil, fmt.Errorf("no such image %s", imageId)
|
||
}
|
||
return input, nil
|
||
}
|
||
|
||
func (gtm *SGuestTemplateManager) validateData(
|
||
ctx context.Context,
|
||
userCred mcclient.TokenCredential,
|
||
ownerId mcclient.IIdentityProvider,
|
||
query jsonutils.JSONObject,
|
||
cinput computeapis.GuestTemplateInput,
|
||
) (computeapis.GuestTemplateInput, error) {
|
||
if cinput.Content == nil {
|
||
return cinput, nil
|
||
}
|
||
content := cinput.Content
|
||
// data := cinput.JSON(cinput)
|
||
// not support guest image and guest snapshot for now
|
||
if content.Contains("instance_snapshot_id") {
|
||
return cinput, httperrors.NewInputParameterError(
|
||
"no support for instance snapshot in guest template for now")
|
||
}
|
||
// I don't hope cinput.Content same with data["content"] will change in GuestManager.validateCreateData
|
||
copy := jsonutils.DeepCopy(content).(*jsonutils.JSONDict)
|
||
input, err := gtm.validateContent(ctx, userCred, ownerId, query, copy)
|
||
if err != nil {
|
||
return cinput, httperrors.NewInputParameterError("%v", err)
|
||
}
|
||
log.Debugf("data: %#v", input)
|
||
// fill field
|
||
cinput.VmemSize = input.VmemSize
|
||
// data.Add(jsonutils.NewInt(int64(input.VmemSize)), "vmem_size")
|
||
cinput.VcpuCount = input.VcpuCount
|
||
// data.Add(jsonutils.NewInt(int64(input.VcpuCount)), "vcpu_count")
|
||
cinput.OsType = input.OsType
|
||
// data.Add(jsonutils.NewString(input.OsType), "os_type")
|
||
cinput.Hypervisor = input.Hypervisor
|
||
// data.Add(jsonutils.NewString(input.Hypervisor), "hypervisor")
|
||
cinput.InstanceType = input.InstanceType
|
||
cinput.CloudregionId = input.PreferRegion
|
||
cinput.BillingType = input.BillingType
|
||
|
||
// fill vpc
|
||
if len(input.Networks) != 0 && len(input.Networks[0].Network) != 0 {
|
||
model, err := NetworkManager.FetchById(input.Networks[0].Network)
|
||
if err != nil {
|
||
return cinput, errors.Wrap(err, "NetworkManager.FetchById")
|
||
}
|
||
net := model.(*SNetwork)
|
||
vpc := net.GetVpc()
|
||
if vpc != nil {
|
||
cinput.VpcId = vpc.Id
|
||
}
|
||
}
|
||
|
||
if len(input.GuestImageID) > 0 {
|
||
cinput.ImageType = IMAGE_TYPE_GUEST
|
||
cinput.ImageId = input.GuestImageID
|
||
// data.Add(jsonutils.NewString(IMAGE_TYPE_GUEST), "image_type")
|
||
// data.Add(jsonutils.NewString(input.GuestImageID), "image_id")
|
||
} else {
|
||
cinput.ImageType = input.GuestImageID
|
||
cinput.ImageId = input.Disks[0].ImageId // if input.Didks is empty???
|
||
// data.Add(jsonutils.NewString(input.Disks[0].ImageId), "image_id")
|
||
// data.Add(jsonutils.NewString(IMAGE_TYPE_NORMAL), "image_type")
|
||
}
|
||
|
||
// hide some properties
|
||
contentDict := content.(*jsonutils.JSONDict)
|
||
contentDict.Remove("name")
|
||
contentDict.Remove("generate_name")
|
||
// "__count__" was converted to "count" by apigateway
|
||
contentDict.Remove("count")
|
||
contentDict.Remove("project_id")
|
||
contentDict.Remove("__count__")
|
||
|
||
cinput.Content = contentDict
|
||
// data.Add(contentDict, "content")
|
||
return cinput, nil
|
||
}
|
||
|
||
func (gt *SGuestTemplate) ValidateUpdateData(
|
||
ctx context.Context,
|
||
userCred mcclient.TokenCredential,
|
||
query jsonutils.JSONObject,
|
||
input computeapis.GuestTemplateUpdateInput,
|
||
) (computeapis.GuestTemplateUpdateInput, error) {
|
||
var err error
|
||
input.GuestTemplateInput, err = GuestTemplateManager.validateData(ctx, userCred, gt.GetOwnerId(), query, input.GuestTemplateInput)
|
||
if err != nil {
|
||
return input, errors.Wrap(err, "GuestTemplateManager.validateData")
|
||
}
|
||
input.SharableVirtualResourceBaseUpdateInput, err = gt.SSharableVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, input.SharableVirtualResourceBaseUpdateInput)
|
||
if err != nil {
|
||
return input, errors.Wrap(err, "SSharableVirtualResourceBase.ValidateUpdateData")
|
||
}
|
||
return input, nil
|
||
}
|
||
|
||
func (manager *SGuestTemplateManager) FetchCustomizeColumns(
|
||
ctx context.Context,
|
||
userCred mcclient.TokenCredential,
|
||
query jsonutils.JSONObject,
|
||
objs []interface{},
|
||
fields stringutils2.SSortedStrings,
|
||
isList bool,
|
||
) []computeapis.GuestTemplateDetails {
|
||
rows := make([]computeapis.GuestTemplateDetails, len(objs))
|
||
|
||
virtRows := manager.SSharableVirtualResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
|
||
crRows := manager.SCloudregionResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
|
||
vpcRows := manager.SVpcResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
|
||
for i := range rows {
|
||
rows[i] = computeapis.GuestTemplateDetails{
|
||
SharableVirtualResourceDetails: virtRows[i],
|
||
CloudregionResourceInfo: crRows[i],
|
||
VpcResourceInfo: vpcRows[i],
|
||
}
|
||
rows[i], _ = objs[i].(*SGuestTemplate).getMoreDetails(ctx, userCred, rows[i])
|
||
}
|
||
|
||
return rows
|
||
}
|
||
|
||
func (gt *SGuestTemplate) GetExtraDetails(
|
||
ctx context.Context,
|
||
userCred mcclient.TokenCredential,
|
||
query jsonutils.JSONObject,
|
||
isList bool,
|
||
) (computeapis.GuestTemplateDetails, error) {
|
||
return computeapis.GuestTemplateDetails{}, nil
|
||
}
|
||
|
||
func (gt *SGuestTemplate) getMoreDetails(ctx context.Context, userCred mcclient.TokenCredential,
|
||
out computeapis.GuestTemplateDetails) (computeapis.GuestTemplateDetails, error) {
|
||
|
||
input, err := cmdline.FetchServerCreateInputByJSON(gt.Content)
|
||
if err != nil {
|
||
return out, err
|
||
}
|
||
configInfo := computeapis.GuestTemplateConfigInfo{}
|
||
if len(input.PreferZone) != 0 {
|
||
zone := ZoneManager.FetchZoneById(input.PreferZone)
|
||
if zone != nil {
|
||
input.PreferZone = zone.GetName()
|
||
out.ZoneId = zone.GetId()
|
||
}
|
||
out.Zone = input.PreferZone
|
||
}
|
||
out.Brand = Hypervisor2Brand(gt.Hypervisor)
|
||
|
||
// metadata
|
||
configInfo.Metadata = input.Metadata
|
||
|
||
// sku deal
|
||
if len(input.InstanceType) > 0 {
|
||
skuOutput := computeapis.GuestTemplateSku{}
|
||
provider := GetDriver(gt.Hypervisor).GetProvider()
|
||
sku, err := ServerSkuManager.FetchSkuByNameAndProvider(input.InstanceType, provider, true)
|
||
if err != nil {
|
||
skuOutput.Name = input.InstanceType
|
||
skuOutput.MemorySizeMb = gt.VmemSize
|
||
skuOutput.CpuCoreCount = gt.VcpuCount
|
||
} else {
|
||
skuOutput.Name = sku.Name
|
||
skuOutput.MemorySizeMb = sku.MemorySizeMB
|
||
skuOutput.CpuCoreCount = sku.CpuCoreCount
|
||
skuOutput.InstanceTypeCategory = sku.InstanceTypeCategory
|
||
skuOutput.InstanceTypeFamily = sku.InstanceTypeFamily
|
||
}
|
||
configInfo.Sku = skuOutput
|
||
}
|
||
|
||
// disk deal
|
||
disks := make([]computeapis.GuestTemplateDisk, len(input.Disks))
|
||
for i := range input.Disks {
|
||
disks[i] = computeapis.GuestTemplateDisk{
|
||
Backend: input.Disks[i].Backend,
|
||
DiskType: input.Disks[i].DiskType,
|
||
Index: input.Disks[i].Index,
|
||
SizeMb: input.Disks[i].SizeMb,
|
||
}
|
||
}
|
||
configInfo.Disks = disks
|
||
|
||
// keypair
|
||
if len(input.KeypairId) > 0 {
|
||
model, err := KeypairManager.FetchByIdOrName(userCred, input.KeypairId)
|
||
if err == nil {
|
||
keypair := model.(*SKeypair)
|
||
configInfo.Keypair = keypair.GetName()
|
||
}
|
||
}
|
||
|
||
// network
|
||
if len(input.Networks) > 0 {
|
||
networkList := make([]computeapis.GuestTemplateNetwork, 0, len(input.Networks))
|
||
networkIdList := make([]string, len(input.Networks))
|
||
for i := range input.Networks {
|
||
networkIdList[i] = input.Networks[i].Network
|
||
}
|
||
networkSet := sets.NewString(networkIdList...)
|
||
|
||
wireQuery := WireManager.Query("id", "vpc_id").SubQuery()
|
||
vpcQuery := VpcManager.Query("id", "name").SubQuery()
|
||
q := NetworkManager.Query("id", "name", "wire_id", "guest_ip_start", "guest_ip_end", "vlan_id")
|
||
if len(networkIdList) == 1 {
|
||
q = q.Equals("id", networkIdList[0])
|
||
} else {
|
||
q = q.In("id", networkIdList)
|
||
}
|
||
q = q.LeftJoin(wireQuery, sqlchemy.Equals(q.Field("wire_id"), wireQuery.Field("id")))
|
||
q = q.LeftJoin(vpcQuery, sqlchemy.Equals(wireQuery.Field("vpc_id"), vpcQuery.Field("id")))
|
||
q = q.AppendField(vpcQuery.Field("id", "vpc_id"), vpcQuery.Field("name", "vpc_name"))
|
||
q.All(&networkList)
|
||
|
||
for _, p := range networkList {
|
||
if networkSet.Has(p.ID) {
|
||
networkSet.Delete(p.ID)
|
||
}
|
||
}
|
||
|
||
// some specified network
|
||
for _, id := range networkSet.UnsortedList() {
|
||
networkList = append(networkList, computeapis.GuestTemplateNetwork{ID: id})
|
||
}
|
||
|
||
configInfo.Nets = networkList
|
||
}
|
||
|
||
if len(input.Secgroups) > 0 {
|
||
q := SecurityGroupManager.Query("id", "name").In("id", input.Secgroups)
|
||
rows, err := q.Rows()
|
||
if err != nil {
|
||
return out, errors.Wrap(err, "SQuery.Rows")
|
||
}
|
||
names := make([]string, 0, len(input.Secgroups))
|
||
for rows.Next() {
|
||
var id, name string
|
||
rows.Scan(&id, &name)
|
||
names = append(names, name)
|
||
}
|
||
rows.Close()
|
||
out.Secgroups = names
|
||
}
|
||
|
||
// isolatedDevices
|
||
if input.IsolatedDevices != nil && len(input.IsolatedDevices) != 0 {
|
||
configInfo.IsolatedDeviceConfig = make([]computeapis.IsolatedDeviceConfig, len(input.IsolatedDevices))
|
||
for i := range configInfo.IsolatedDeviceConfig {
|
||
configInfo.IsolatedDeviceConfig[i] = *input.IsolatedDevices[i]
|
||
}
|
||
}
|
||
|
||
// fill image info
|
||
switch gt.ImageType {
|
||
case IMAGE_TYPE_NORMAL:
|
||
image, err := CachedimageManager.getImageInfo(ctx, userCred, gt.ImageId, false)
|
||
if err == nil {
|
||
configInfo.Image = image.Name
|
||
} else {
|
||
configInfo.Image = gt.ImageId
|
||
}
|
||
case IMAGE_TYPE_GUEST:
|
||
s := auth.GetSession(ctx, userCred, options.Options.Region, "")
|
||
ret, err := modules.GuestImages.Get(s, gt.ImageId, jsonutils.JSONNull)
|
||
if err != nil || !ret.Contains("id") {
|
||
configInfo.Image = gt.ImageId
|
||
} else {
|
||
name, _ := ret.GetString("id")
|
||
configInfo.Image = name
|
||
}
|
||
default:
|
||
// no arrivals
|
||
}
|
||
|
||
// reset_password
|
||
if input.ResetPassword == nil {
|
||
configInfo.ResetPassword = true
|
||
} else {
|
||
configInfo.ResetPassword = *input.ResetPassword
|
||
}
|
||
|
||
out.ConfigInfo = configInfo
|
||
return out, nil
|
||
}
|
||
|
||
func (gt *SGuestTemplate) PerformPublic(
|
||
ctx context.Context,
|
||
userCred mcclient.TokenCredential,
|
||
query jsonutils.JSONObject,
|
||
data apis.PerformPublicProjectInput,
|
||
) (jsonutils.JSONObject, error) {
|
||
|
||
// image, network, secgroup, instancegroup
|
||
input, err := cmdline.FetchServerCreateInputByJSON(gt.Content)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "fail to convert content of guest template to ServerCreateInput")
|
||
}
|
||
|
||
// check for below private resource in the guest template
|
||
privateResource := map[string]int{
|
||
"keypair": len(input.KeypairId),
|
||
"instance group": len(input.InstanceGroupIds),
|
||
"instance snapshot": len(input.InstanceSnapshotId),
|
||
}
|
||
for k, v := range privateResource {
|
||
if v > 0 {
|
||
return nil, gt.genForbiddenError(k, "", "")
|
||
}
|
||
}
|
||
|
||
targetScopeStr := data.Scope
|
||
targetScope := rbacutils.String2ScopeDefault(targetScopeStr, rbacutils.ScopeSystem)
|
||
|
||
// check if secgroup is public
|
||
if len(input.SecgroupId) > 0 {
|
||
model, err := SecurityGroupManager.FetchByIdOrName(userCred, input.SecgroupId)
|
||
if err != nil {
|
||
return nil, httperrors.NewResourceNotFoundError("there is no such secgroup %s descripted by guest template",
|
||
input.SecgroupId)
|
||
}
|
||
secgroup := model.(*SSecurityGroup)
|
||
sgScope := rbacutils.String2Scope(secgroup.PublicScope)
|
||
if !secgroup.IsPublic || !sgScope.HigherEqual(targetScope) {
|
||
return nil, gt.genForbiddenError("security group", input.SecgroupId, string(targetScope))
|
||
}
|
||
}
|
||
|
||
// check if networks is public
|
||
if len(input.Networks) > 0 {
|
||
for i := range input.Networks {
|
||
str := input.Networks[i].Network
|
||
model, err := NetworkManager.FetchByIdOrName(userCred, str)
|
||
if err != nil {
|
||
return nil, httperrors.NewResourceNotFoundError(
|
||
"there is no such secgroup %s descripted by guest template", str)
|
||
}
|
||
network := model.(*SNetwork)
|
||
netScope := rbacutils.String2Scope(network.PublicScope)
|
||
if !network.IsPublic || !netScope.HigherEqual(targetScope) {
|
||
return nil, gt.genForbiddenError("network", str, string(targetScope))
|
||
}
|
||
}
|
||
}
|
||
|
||
// check if image is public
|
||
var (
|
||
isPublic bool
|
||
publicScope string
|
||
)
|
||
switch gt.ImageType {
|
||
case IMAGE_TYPE_NORMAL:
|
||
image, err := CachedimageManager.GetImageById(ctx, userCred, gt.ImageId, false)
|
||
if err != nil {
|
||
return nil, errors.Wrapf(err, "fail to fetch image %s descripted by guest template", gt.ImageId)
|
||
}
|
||
isPublic, publicScope = image.IsPublic, image.PublicScope
|
||
case IMAGE_TYPE_GUEST:
|
||
s := auth.GetSession(ctx, userCred, options.Options.Region, "")
|
||
ret, err := modules.GuestImages.Get(s, gt.ImageId, jsonutils.JSONNull)
|
||
if err != nil {
|
||
return nil, errors.Wrapf(err, "fail to fetch guest image %s descripted by guest template", gt.ImageId)
|
||
}
|
||
isPublic = jsonutils.QueryBoolean(ret, "is_public", false)
|
||
publicScope, _ = ret.GetString("public_scope")
|
||
default:
|
||
//no arrivals
|
||
}
|
||
igScope := rbacutils.String2Scope(publicScope)
|
||
if !isPublic || !igScope.HigherEqual(targetScope) {
|
||
return nil, gt.genForbiddenError("image", "", string(targetScope))
|
||
}
|
||
|
||
return gt.SSharableVirtualResourceBase.PerformPublic(ctx, userCred, query, data)
|
||
}
|
||
|
||
func (gt *SGuestTemplate) genForbiddenError(resourceName, resourceStr, scope string) error {
|
||
var (
|
||
msgFmt string
|
||
msgArgs []interface{}
|
||
)
|
||
if resourceStr == "" {
|
||
msgFmt = "the %s in guest template is not a public resource"
|
||
msgArgs = []interface{}{resourceName}
|
||
} else {
|
||
msgFmt = "the %s %q in guest template is not a public resource"
|
||
msgArgs = []interface{}{resourceName, resourceStr}
|
||
}
|
||
if scope != "" {
|
||
msgFmt += " in %s scope"
|
||
msgArgs = append(msgArgs, scope)
|
||
}
|
||
return httperrors.NewForbiddenError(msgFmt, msgArgs...)
|
||
}
|
||
|
||
func (gt *SGuestTemplate) ValidateDeleteCondition(ctx context.Context) error {
|
||
// check service catelog
|
||
q := ServiceCatalogManager.Query("name").Equals("guest_template_id", gt.Id)
|
||
names := make([]struct{ Name string }, 0, 1)
|
||
err := q.All(&names)
|
||
if err != nil {
|
||
return errors.Wrap(err, "SQuery.All")
|
||
}
|
||
if len(names) > 0 {
|
||
return httperrors.NewForbiddenError("guest template %s used by service catalog %s", gt.Id, names[0].Name)
|
||
}
|
||
// check scaling group
|
||
q = ScalingGroupManager.Query("name").Equals("guest_template_id", gt.Id)
|
||
names = make([]struct{ Name string }, 0, 1)
|
||
err = q.All(&names)
|
||
if err != nil {
|
||
return errors.Wrap(err, "SQuery.All")
|
||
}
|
||
if len(names) > 0 {
|
||
return httperrors.NewForbiddenError("guest template %s used by scalig group %s", gt.Id, names[0].Name)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// 主机模板列表
|
||
func (manager *SGuestTemplateManager) ListItemFilter(
|
||
ctx context.Context,
|
||
q *sqlchemy.SQuery,
|
||
userCred mcclient.TokenCredential,
|
||
input computeapis.GuestTemplateListInput,
|
||
) (*sqlchemy.SQuery, error) {
|
||
var err error
|
||
q, err = manager.SSharableVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, input.SharableVirtualResourceListInput)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "SSharableVirtualResourceBaseManager.ListItemFilter")
|
||
}
|
||
q, err = manager.SCloudregionResourceBaseManager.ListItemFilter(ctx, q, userCred, input.RegionalFilterListInput)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.ListItemFilter")
|
||
}
|
||
if len(input.VpcId) > 0 {
|
||
q, err = manager.SVpcResourceBaseManager.ListItemFilter(ctx, q, userCred, input.VpcFilterListInput)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "SVpcResourceBaseManager.ListItemFilter")
|
||
}
|
||
}
|
||
if len(input.BillingType) > 0 {
|
||
q = q.Equals("billing_type", input.BillingType)
|
||
}
|
||
if len(input.Brand) > 0 {
|
||
q = q.Equals("hypervisor", Brand2Hypervisor(input.Brand))
|
||
}
|
||
return q, nil
|
||
}
|
||
|
||
func (manager *SGuestTemplateManager) OrderByExtraFields(
|
||
ctx context.Context,
|
||
q *sqlchemy.SQuery,
|
||
userCred mcclient.TokenCredential,
|
||
input computeapis.GuestTemplateListInput,
|
||
) (*sqlchemy.SQuery, error) {
|
||
var err error
|
||
|
||
q, err = manager.SSharableVirtualResourceBaseManager.OrderByExtraFields(ctx, q, userCred, input.SharableVirtualResourceListInput)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "SSharableVirtualResourceBaseManager.OrderByExtraFields")
|
||
}
|
||
q, err = manager.SCloudregionResourceBaseManager.OrderByExtraFields(ctx, q, userCred, input.RegionalFilterListInput)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.OrderByExtraFields")
|
||
}
|
||
q, err = manager.SVpcResourceBaseManager.OrderByExtraFields(ctx, q, userCred, input.VpcFilterListInput)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "SVpcResourceBaseManager.OrderByExtraFields")
|
||
}
|
||
return q, nil
|
||
}
|
||
|
||
func (manager *SGuestTemplateManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
|
||
var err error
|
||
|
||
q, err = manager.SSharableVirtualResourceBaseManager.QueryDistinctExtraField(q, field)
|
||
if err == nil {
|
||
return q, nil
|
||
}
|
||
q, err = manager.SCloudregionResourceBaseManager.QueryDistinctExtraField(q, field)
|
||
if err == nil {
|
||
return q, nil
|
||
}
|
||
q, err = manager.SVpcResourceBaseManager.QueryDistinctExtraField(q, field)
|
||
if err == nil {
|
||
return q, nil
|
||
}
|
||
|
||
return q, httperrors.ErrNotFound
|
||
}
|
||
|
||
type SGuestTemplateValidate struct {
|
||
Hypervisor string
|
||
CloudregionId string
|
||
VpcId string
|
||
NetworkIds []string
|
||
}
|
||
|
||
func (gt *SGuestTemplate) Validate(ctx context.Context, userCred mcclient.TokenCredential,
|
||
ownerId mcclient.IIdentityProvider, stv SGuestTemplateValidate) (bool, string) {
|
||
if stv.Hypervisor != "" && gt.Hypervisor != stv.Hypervisor {
|
||
return false, fmt.Sprintf("GuestTemplate has mismatched hypervisor, need %s but %s", stv.Hypervisor, gt.Hypervisor)
|
||
}
|
||
if stv.CloudregionId != "" && gt.CloudregionId != stv.CloudregionId {
|
||
return false, fmt.Sprintf("GuestTemplate has mismatched cloudregion, need %s but %s", stv.CloudregionId, gt.CloudregionId)
|
||
}
|
||
if stv.VpcId != "" && gt.VpcId != "" && stv.VpcId != gt.VpcId {
|
||
return false, fmt.Sprintf("GuestTemplate has mismatched vpc, need %s bu %s", stv.VpcId, gt.VpcId)
|
||
}
|
||
|
||
// check networks
|
||
input, err := GuestTemplateManager.validateContent(ctx, userCred, ownerId, jsonutils.NewDict(), gt.Content.(*jsonutils.JSONDict))
|
||
if err != nil {
|
||
return false, err.Error()
|
||
}
|
||
if len(input.Networks) != 0 && len(input.Networks[0].Network) != 0 {
|
||
for i := range input.Networks {
|
||
if !utils.IsInStringArray(input.Networks[i].Network, stv.NetworkIds) {
|
||
return false, fmt.Sprintf("GuestTemplate's network '%s' not in networks '%s'", input.Networks[i].Network, stv.NetworkIds)
|
||
}
|
||
}
|
||
}
|
||
return true, ""
|
||
}
|
||
|
||
func (manager *SGuestTemplateManager) ListItemExportKeys(ctx context.Context,
|
||
q *sqlchemy.SQuery,
|
||
userCred mcclient.TokenCredential,
|
||
keys stringutils2.SSortedStrings,
|
||
) (*sqlchemy.SQuery, error) {
|
||
var err error
|
||
|
||
q, err = manager.SSharableVirtualResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "SSharableVirtualResourceBaseManager.ListItemExportKeys")
|
||
}
|
||
if keys.ContainsAny(manager.SCloudregionResourceBaseManager.GetExportKeys()...) {
|
||
q, err = manager.SCloudregionResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.ListItemExportKeys")
|
||
}
|
||
}
|
||
if keys.ContainsAny(manager.SVpcResourceBaseManager.GetExportKeys()...) {
|
||
q, err = manager.SVpcResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys)
|
||
if err != nil {
|
||
return nil, errors.Wrap(err, "SVpcResourceBaseManager.ListItemExportKeys")
|
||
}
|
||
}
|
||
|
||
return q, nil
|
||
}
|
||
|
||
func (g *SGuest) AllowPerformSaveTemplate(ctx context.Context, userCred mcclient.TokenCredential,
|
||
query jsonutils.JSONObject) bool {
|
||
return g.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, g, "")
|
||
}
|
||
|
||
func (g *SGuest) PerformSaveTemplate(ctx context.Context, userCred mcclient.TokenCredential,
|
||
query jsonutils.JSONObject, input computeapis.GuestSaveToTemplateInput) (jsonutils.JSONObject, error) {
|
||
g.SetStatus(userCred, computeapis.VM_TEMPLATE_SAVING, "save to template")
|
||
|
||
if len(input.Name) == 0 && len(input.GenerateName) == 0 {
|
||
input.GenerateName = fmt.Sprintf("%s-template", g.Name)
|
||
}
|
||
data := jsonutils.Marshal(input).(*jsonutils.JSONDict)
|
||
if task, err := taskman.TaskManager.NewTask(ctx, "GuestSaveTemplateTask", g, userCred, data, "", "", nil); err != nil {
|
||
return nil, errors.Wrap(err, "Unbale to init 'GuestSaveTemplateTask'")
|
||
} else {
|
||
task.ScheduleRun(nil)
|
||
}
|
||
return nil, nil
|
||
}
|
||
|
||
func (gt *SGuestTemplate) AllowPerformInspect(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
|
||
return gt.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, gt, "inspect")
|
||
}
|
||
|
||
func (gt *SGuestTemplate) PerformInspect(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||
return nil, gt.inspect(ctx, userCred)
|
||
}
|
||
|
||
func (gt *SGuestTemplate) inspect(ctx context.Context, userCred mcclient.TokenCredential) error {
|
||
_, err := GuestTemplateManager.validateContent(ctx, userCred, gt.GetOwnerId(), jsonutils.NewDict(), gt.Content.(*jsonutils.JSONDict))
|
||
if err == nil {
|
||
gt.updateCheckTime()
|
||
gt.SetStatus(userCred, computeapis.GT_READY, "inspect successfully")
|
||
logclient.AddSimpleActionLog(gt, logclient.ACT_HEALTH_CHECK, "", userCred, true)
|
||
return nil
|
||
}
|
||
// invalid
|
||
gt.updateCheckTime()
|
||
reason := fmt.Sprintf("During the inspection, the guest template is not available: %s", err.Error())
|
||
gt.SetStatus(userCred, computeapis.GT_INVALID, reason)
|
||
logclient.AddSimpleActionLog(gt, logclient.ACT_HEALTH_CHECK, reason, userCred, false)
|
||
return nil
|
||
}
|
||
|
||
func (gm *SGuestTemplateManager) InspectAllTemplate(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {
|
||
lastCheckTime := time.Now().Add(time.Duration(-options.Options.GuestTemplateCheckInterval) * time.Hour)
|
||
q := gm.Query()
|
||
q = q.Filter(sqlchemy.OR(sqlchemy.IsNull(q.Field("last_check_time")), sqlchemy.LE(q.Field("last_check_time"),
|
||
lastCheckTime)))
|
||
gts := make([]SGuestTemplate, 0, 10)
|
||
err := db.FetchModelObjects(gm, q, >s)
|
||
if err != nil {
|
||
log.Errorf("Unable to fetch all guest templates that need to check: %s", err.Error())
|
||
return
|
||
}
|
||
for i := range gts {
|
||
gts[i].inspect(ctx, userCred)
|
||
}
|
||
}
|