mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-05-06 21:52:54 +08:00
829 lines
29 KiB
Go
829 lines
29 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/rbacscope"
|
||
"yunion.io/x/pkg/util/sets"
|
||
"yunion.io/x/pkg/utils"
|
||
"yunion.io/x/sqlchemy"
|
||
|
||
"yunion.io/x/onecloud/pkg/apis"
|
||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||
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/image"
|
||
"yunion.io/x/onecloud/pkg/util/logclient"
|
||
"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")
|
||
}
|
||
|
||
if !input.Content.Contains("name") && !input.Content.Contains("generate_name") {
|
||
input.Content.Set("generate_name", jsonutils.NewString(input.Name))
|
||
}
|
||
|
||
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(ctx, 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,
|
||
computeapis.HYPERVISOR_CNWARE: computeapis.CLOUD_PROVIDER_CNWARE,
|
||
}
|
||
|
||
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,
|
||
computeapis.CLOUD_PROVIDER_CNWARE: computeapis.HYPERVISOR_CNWARE,
|
||
}
|
||
|
||
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) {
|
||
// hack
|
||
if !content.Contains("name") && !content.Contains("generate_name") {
|
||
content.Set("generate_name", jsonutils.NewString("fake_name"))
|
||
}
|
||
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, false)
|
||
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
|
||
content.Remove("name")
|
||
content.Remove("generate_name")
|
||
// "__count__" was converted to "count" by apigateway
|
||
content.Remove("count")
|
||
content.Remove("project_id")
|
||
content.Remove("__count__")
|
||
|
||
cinput.Content = content
|
||
// 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) 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{}
|
||
sku, err := ServerSkuManager.FetchSkuByNameAndProvider(input.InstanceType, out.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(ctx, 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 := image.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 := rbacscope.String2ScopeDefault(targetScopeStr, rbacscope.ScopeSystem)
|
||
|
||
// check if secgroup is public
|
||
if len(input.SecgroupId) > 0 {
|
||
model, err := SecurityGroupManager.FetchByIdOrName(ctx, 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 := rbacscope.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(ctx, userCred, str)
|
||
if err != nil {
|
||
return nil, httperrors.NewResourceNotFoundError(
|
||
"there is no such secgroup %s descripted by guest template", str)
|
||
}
|
||
network := model.(*SNetwork)
|
||
netScope := rbacscope.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 := image.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 := rbacscope.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, info jsonutils.JSONObject) 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.CloudEnv) > 0 {
|
||
cloudregions := CloudregionManager.Query().SubQuery()
|
||
q = q.Join(cloudregions, sqlchemy.Equals(q.Field("cloudregion_id"), cloudregions.Field("id")))
|
||
switch input.CloudEnv {
|
||
case api.CLOUD_ENV_PUBLIC_CLOUD:
|
||
q = q.Filter(sqlchemy.In(cloudregions.Field("provider"), CloudproviderManager.GetPublicProviderProvidersQuery()))
|
||
case api.CLOUD_ENV_PRIVATE_CLOUD:
|
||
q = q.Filter(sqlchemy.In(cloudregions.Field("provider"), CloudproviderManager.GetPrivateProviderProvidersQuery()))
|
||
case api.CLOUD_ENV_ON_PREMISE:
|
||
q = q.Filter(sqlchemy.Equals(cloudregions.Field("provider"), api.CLOUD_PROVIDER_ONECLOUD))
|
||
case api.CLOUD_ENV_PRIVATE_ON_PREMISE:
|
||
q = q.Filter(sqlchemy.OR(
|
||
sqlchemy.Equals(cloudregions.Field("provider"), api.CLOUD_PROVIDER_ONECLOUD),
|
||
sqlchemy.In(cloudregions.Field("provider"), CloudproviderManager.GetPrivateProviderProvidersQuery()),
|
||
))
|
||
}
|
||
}
|
||
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) PerformSaveTemplate(ctx context.Context, userCred mcclient.TokenCredential,
|
||
query jsonutils.JSONObject, input computeapis.GuestSaveToTemplateInput) (jsonutils.JSONObject, error) {
|
||
g.SetStatus(ctx, 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) 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(ctx, 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(ctx, 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)
|
||
}
|
||
}
|