Merge pull request #3805 from rainzm/feature/guest_template

feature: Guest Template & Service Catalog
This commit is contained in:
Zexi Li
2019-12-09 11:31:08 +08:00
committed by GitHub
13 changed files with 1087 additions and 7 deletions
+168
View File
@@ -0,0 +1,168 @@
// 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 shell
import (
"fmt"
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/mcclient/options"
)
func init() {
type GuestTemplateListOptions struct {
options.BaseListOptions
}
R(&GuestTemplateListOptions{}, "server-template-list", "List server template", func(s *mcclient.ClientSession,
opts *GuestTemplateListOptions) error {
params, err := options.ListStructToParams(opts)
if err != nil {
return err
}
result, err := modules.GuestTemplate.List(s, params)
if err != nil {
return err
}
printList(result, modules.GuestTemplate.GetColumns(s))
return nil
})
type GuestTemplateCreateOptions struct {
options.ServerCreateOptionalOptions
NAME string `help:"Name of server template" json:"-"`
}
R(&GuestTemplateCreateOptions{}, "server-template-create", "Create a server template",
func(s *mcclient.ClientSession,
opts *GuestTemplateCreateOptions) error {
params, err := opts.OptionalParams()
if err != nil {
return err
}
if options.BoolV(opts.DryRun) {
fmt.Println("no support operator")
return nil
}
dict := jsonutils.NewDict()
if opts.GenerateName {
dict.Add(jsonutils.NewString(opts.NAME), "generate_name")
} else {
dict.Add(jsonutils.NewString(opts.NAME), "name")
}
dict.Add(params.JSON(params), "content")
tem, err := modules.GuestTemplate.Create(s, dict)
if err != nil {
return err
}
printObject(tem)
return nil
})
type GuestTemplateUpdateOptions struct {
options.ServerCreateOptionalOptions
ID string `help:"ID of server template"`
name string `help:"name of server template"`
}
R(&GuestTemplateUpdateOptions{}, "server-template-update", "Update a server template",
func(s *mcclient.ClientSession, opts *GuestTemplateUpdateOptions) error {
params, err := opts.OptionalParams()
if err != nil {
return err
}
if options.BoolV(opts.DryRun) {
fmt.Println("no support operator")
return nil
}
dict := jsonutils.NewDict()
if len(opts.name) != 0 {
dict.Add(jsonutils.NewString(opts.name), "name")
}
dict.Add(params.JSON(params), "content")
tem, err := modules.GuestTemplate.Update(s, opts.ID, dict)
if err != nil {
return err
}
printObject(tem)
return nil
})
type GuestTemplateOptions struct {
ID string `help:"ID or Name of server template"`
}
R(&GuestTemplateOptions{}, "server-template-show", "Show a server template",
func(s *mcclient.ClientSession, opts *GuestTemplateOptions) error {
tem, err := modules.GuestTemplate.Get(s, opts.ID, jsonutils.JSONNull)
if err != nil {
return err
}
printObject(tem)
return nil
})
R(&GuestTemplateOptions{}, "server-tempalte-delete", "Delete a server template",
func(s *mcclient.ClientSession, opts *GuestTemplateOptions) error {
tem, err := modules.GuestTemplate.Delete(s, opts.ID, jsonutils.JSONNull)
if err != nil {
return err
}
printObject(tem)
return nil
},
)
R(&GuestTemplateOptions{}, "server-template-private", "Private server template",
func(s *mcclient.ClientSession, opts *GuestTemplateOptions) error {
tem, err := modules.GuestTemplate.PerformAction(s, opts.ID, "private", jsonutils.JSONNull)
if err != nil {
return err
}
printObject(tem)
return nil
},
)
type GuestTemplatePublicOptions struct {
ID string `help:"ID or Name of server template"`
PublicScope string `help:"public scope"`
}
R(&GuestTemplatePublicOptions{}, "server-template-public", "Public server template",
func(s *mcclient.ClientSession, opts *GuestTemplatePublicOptions) error {
dict := jsonutils.NewDict()
if len(opts.PublicScope) != 0 {
dict.Add(jsonutils.NewString(opts.PublicScope), "public_scope")
}
tem, err := modules.GuestTemplate.PerformAction(s, opts.ID, "public", dict)
if err != nil {
return err
}
printObject(tem)
return nil
},
)
}
+162
View File
@@ -0,0 +1,162 @@
// 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 shell
import (
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/mcclient/options"
)
func init() {
type ServiceCatalogListOpions struct {
options.BaseListOptions
}
R(&ServiceCatalogListOpions{}, "service-catalog-list", "List service catalog", func(s *mcclient.ClientSession,
opts *ServiceCatalogListOpions) error {
params, err := options.ListStructToParams(opts)
if err != nil {
return err
}
ret, err := modules.ServiceCatalog.List(s, params)
if err != nil {
return err
}
printList(ret, modules.ServiceCatalog.GetColumns(s))
return nil
})
type ServiceCatalogCreateOptions struct {
NAME string `help:"Name of service catalog"`
GuestTemplate string `help:"guest template of service catalog"`
IconUrl string `help:"icon url of service catalog"`
GenerateName bool `help:"whether to generate name"`
}
R(&ServiceCatalogCreateOptions{}, "service-catalog-create", "Create a service catalog",
func(s *mcclient.ClientSession, opts *ServiceCatalogCreateOptions) error {
params := jsonutils.NewDict()
if opts.GenerateName {
params.Add(jsonutils.NewString(opts.NAME), "generate_name")
} else {
params.Add(jsonutils.NewString(opts.NAME), "name")
}
if len(opts.GuestTemplate) != 0 {
params.Add(jsonutils.NewString(opts.GuestTemplate), "guest_template")
}
if len(opts.IconUrl) != 0 {
params.Add(jsonutils.NewString(opts.IconUrl), "icon_url")
}
serviceCatalog, err := modules.ServiceCatalog.Create(s, params)
if err != nil {
return err
}
printObject(serviceCatalog)
return nil
},
)
type ServiceCatalogOptions struct {
ID string `help:"ID or name of service catalog"`
}
R(&ServiceCatalogOptions{}, "service-catalog-show", "show a service catalog", func(s *mcclient.ClientSession,
opts *ServiceCatalogOptions) error {
sc, err := modules.ServiceCatalog.Get(s, opts.ID, jsonutils.JSONNull)
if err != nil {
return err
}
printObject(sc)
return nil
})
R(&ServiceCatalogOptions{}, "service-catalog-delete", "delete a service catalog", func(s *mcclient.ClientSession,
opts *ServiceCatalogOptions) error {
sc, err := modules.ServiceCatalog.Delete(s, opts.ID, jsonutils.JSONNull)
if err != nil {
return err
}
printObject(sc)
return nil
})
type ServiceCatalogUpdateOptions struct {
ServiceCatalogOptions
Name string `help:"Name of service catalog"`
GuestTemplate string `help:"guest template of service catalog"`
IconUrl string `help:"icon url of service catalog"`
}
R(&ServiceCatalogUpdateOptions{}, "service-catalog-update", "update a service catalog",
func(s *mcclient.ClientSession, opts *ServiceCatalogUpdateOptions) error {
params := jsonutils.NewDict()
if len(opts.Name) > 0 {
params.Add(jsonutils.NewString(opts.Name), "name")
}
if len(opts.GuestTemplate) > 0 {
params.Add(jsonutils.NewString(opts.GuestTemplate), "guest_template")
}
if len(opts.IconUrl) > 0 {
params.Add(jsonutils.NewString(opts.IconUrl), "icon_url")
}
sc, err := modules.ServiceCatalog.Update(s, opts.ID, params)
if err != nil {
return err
}
printObject(sc)
return nil
},
)
type ServiceCatalogDeployOptions struct {
ServiceCatalogOptions
Name string `help:"Name of guest"`
GenerateName bool `help:"whether to generate name for guest"`
Count int `help:"count of guest"`
ProjectID string `help:"project id of guest"`
}
R(&ServiceCatalogDeployOptions{}, "service-catalog-deploy", "deploy", func(s *mcclient.ClientSession,
opts *ServiceCatalogDeployOptions) error {
params := jsonutils.NewDict()
if opts.GenerateName {
params.Add(jsonutils.NewString(opts.Name), "generate_name")
} else {
params.Add(jsonutils.NewString(opts.Name), "name")
}
if opts.Count != 0 {
params.Add(jsonutils.NewInt(int64(opts.Count)), "count")
}
if len(opts.ProjectID) > 0 {
params.Add(jsonutils.NewString(opts.ProjectID), "project_id")
}
sc, err := modules.ServiceCatalog.PerformAction(s, opts.ID, "deploy", params)
if err != nil {
return err
}
printObject(sc)
return nil
})
}
+75
View File
@@ -0,0 +1,75 @@
package compute
import (
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/apis"
)
type GuesttemplateCreateInput struct {
apis.Meta
// description: guest template name
// unique: true
// required: true
// example: hello
Name string `json:"name"`
// description: the content of guest template
// required: true
Content jsonutils.JSONObject `json:"content"`
}
type GuesttemplateDetails struct {
apis.Meta
apis.SharableVirtualResourceDetails
SGuestTemplate
Config GuesttemplateConfigInfo `json:"config"`
}
type GuesttemplateConfigInfo struct {
Region string `json:"region"`
Zone string `json:"zone"`
Hypervisor string `json:"hypervisor"`
OsType string `json:"os_type"`
Sku GuesttemplateSku `json:"sku"`
Disks []GuesttemplateDisk `json:"disks"`
Keypair string `json:"keypair"`
Nets []GuesttemplateNetwork `json:"nets"`
Secgroup string `json:"secgroup"`
IsolatedDeviceConfig []IsolatedDeviceConfig `json:"isolated_device_config"`
Image string `json:"image"`
}
type GuesttemplateDisk struct {
Backend string `json:"backend"`
DiskType string `json:"disk_type"`
Index int `json:"index"`
SizeMb int `json:"size_mb"`
}
type GuesttemplateNetwork struct {
ID string `json:"id"`
Name string `json:"name"`
GuestIpStart string `json:"guest_ip_start"`
GuestIpEnd string `json:"guest_up_end"`
VlanId int `json:"vlan_id"`
}
type GuesttemplateSku struct {
Name string `json:"name"`
CpuCoreCount int `json:"cpu_core_count"`
MemorySizeMb int `json:"memory_size_mb"`
InstanceTypeCategory string `json:"instance_type_category`
InstanceTypeFamily string `json:"instance_type_family"`
}
type GuesttemplatePublicInput struct {
apis.Meta
// description: the scope about public operator
// required: true
// example: system
Scope string `json:"scope"`
}
+36
View File
@@ -0,0 +1,36 @@
package compute
import "yunion.io/x/onecloud/pkg/apis"
type ServiceCatalogCreateInput struct {
apis.Meta
// description: service catalog name
// uniqure: true
// required: true
// example: hello
Name string `json:"name`
// description: service catalog icon url
// example: https://yunion.io/files/hello.png
IconUrl string `json:"icon_url"`
// description: the id or name of guest template
// example: good
GuestTemplate string `json:"guest_template"`
}
type ServiceCatalogDeploy struct {
// description: name of the new vm
// example: hello
Name string `json:"name"`
// description: generate name automatically if name is repeated, and only one of name and this shoudle be given
// example: hello
GenerateName string `json:"generate_name"`
// description: the count of the new vm
// example: 1
Count int `json:"count"`
}
+19
View File
@@ -549,6 +549,18 @@ type SGuestJointsBase struct {
GuestId string `json:"guest_id"`
}
// SGuestTemplate is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SGuestTemplate.
type SGuestTemplate struct {
apis.SSharableVirtualResourceBase
VcpuCount int `json:"vcpu_count"`
VmemSize int `json:"vmem_size"`
OsType string `json:"os_type"`
ImageType string `json:"image_type"`
ImageId string `json:"image_id"`
Hypervisor string `json:"hypervisor"`
Content interface{} `json:"content"`
}
// SGuestdisk is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SGuestdisk.
type SGuestdisk struct {
SGuestJointsBase
@@ -1191,6 +1203,13 @@ type SServerSku struct {
Provider string `json:"provider"`
}
// SServiceCatalog is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SServiceCatalog.
type SServiceCatalog struct {
apis.SSharableVirtualResourceBase
IconUrl string `json:"icon_url"`
GuestTemplateID string `json:"guest_template_id"`
}
// SSnapshot is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SSnapshot.
type SSnapshot struct {
apis.SVirtualResourceBase
+1
View File
@@ -48,6 +48,7 @@ type SImage struct {
SizeBytes int64 `json:"size"`
Status string
// UpdatedAt time.Time
PublicScope string
}
func CloudImage2Image(image ICloudImage) SImage {
+397
View File
@@ -0,0 +1,397 @@
// 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"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/sets"
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/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"
)
const (
IMAGE_TYPE_NORMAL = "normal"
IMAGE_TYPE_GUEST = "guest"
)
type SGuestTemplateManager struct {
db.SSharableVirtualResourceBaseManager
}
type SGuestTemplate struct {
db.SSharableVirtualResourceBase
VcpuCount int `nullable:"false" default:"1" create:"optional"`
VmemSize int `nullable:"false" create:"optional"`
OsType string `width:"36" charset:"ascii" nullable:"true" create:"optional"`
ImageType string `width:"10" charset:"ascii" nullabel:"true" default:"normal" create:"optional"`
ImageId string `width:"128" charset:"ascii" create:"optional"`
Hypervisor string `width:"16" charset:"ascii" default:"kvm" create:"optional"`
Content jsonutils.JSONObject `nullable:"false" list:"user" update:"user" create:"optional"`
}
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) (*jsonutils.JSONDict, error) {
if input.Content == nil {
return nil, httperrors.NewMissingParameterError("content")
}
data, err := gtm.validateData(ctx, userCred, ownerId, query, input)
if err != nil {
return nil, err
}
return gtm.SSharableVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, data)
}
func (gt *SGuestTemplate) PostCreate(ctx context.Context, userCred mcclient.TokenCredential,
ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
logclient.AddActionLogWithContext(ctx, gt, logclient.ACT_CREATE, nil, userCred, true)
}
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)
}
func (gtm *SGuestTemplateManager) validateData(ctx context.Context, userCred mcclient.TokenCredential,
ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, cinput *computeapis.GuesttemplateCreateInput) (*jsonutils.JSONDict, error) {
if cinput.Content == nil {
return cinput.JSON(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 nil, 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 := GuestManager.validateCreateData(ctx, userCred, ownerId, query, copy)
if err != nil {
return nil, httperrors.NewInputParameterError(err.Error())
}
// fill field
data.Add(jsonutils.NewInt(int64(input.VmemSize)), "vmem_size")
data.Add(jsonutils.NewInt(int64(input.VcpuCount)), "vcpu_count")
data.Add(jsonutils.NewString(input.OsType), "os_type")
data.Add(jsonutils.NewString(input.Hypervisor), "hypervisor")
if len(input.GuestImageID) > 0 {
data.Add(jsonutils.NewString(IMAGE_TYPE_GUEST), "image_type")
data.Add(jsonutils.NewString(input.GuestImageID), "image_id")
} else {
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")
data.Add(contentDict, "content")
return data, nil
}
func (gt *SGuestTemplate) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, cinput *computeapis.GuesttemplateCreateInput) (*jsonutils.JSONDict, error) {
data, err := GuestTemplateManager.validateData(ctx, userCred, gt.GetOwnerId(), query, cinput)
if err != nil {
return nil, nil
}
return gt.SSharableVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, data)
}
func (gt *SGuestTemplate) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject) (*computeapis.GuesttemplateDetails, error) {
out := &computeapis.GuesttemplateDetails{}
err := gt.SSharableVirtualResourceBase.GetExtraDetailsV2(ctx, userCred, query, &out.SharableVirtualResourceDetails)
if err != nil {
return out, err
}
gt.getMoreDetailsV2(ctx, userCred, out)
return out, nil
}
func (gt *SGuestTemplate) getMoreDetailsV2(ctx context.Context, userCred mcclient.TokenCredential,
out *computeapis.GuesttemplateDetails) {
input, err := cmdline.FetchServerCreateInputByJSON(gt.Content)
if err != nil {
return
}
configInfo := computeapis.GuesttemplateConfigInfo{}
if len(input.PreferRegion) != 0 {
region := CloudregionManager.FetchRegionById(input.PreferRegion)
if region != nil {
input.PreferRegion = region.GetName()
}
configInfo.Region = input.PreferRegion
}
if len(input.PreferZone) != 0 {
zone := ZoneManager.FetchZoneById(input.PreferZone)
if zone != nil {
input.PreferZone = zone.GetName()
}
configInfo.Zone = input.PreferZone
}
configInfo.Hypervisor = gt.Hypervisor
configInfo.OsType = gt.OsType
// 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.FetchById(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...)
q := NetworkManager.Query("id", "name", "guest_ip_start", "guest_ip_end", "vlan_id").In("id", networkIdList)
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
}
// secgroup
if len(input.SecgroupId) > 0 {
secgroup := SecurityGroupManager.FetchSecgroupById(input.SecgroupId)
if secgroup != nil {
input.SecgroupId = secgroup.GetName()
}
configInfo.Secgroup = input.SecgroupId
}
// 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
}
out.Config = configInfo
return
}
func (gt *SGuestTemplate) PerformPublic(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, data jsonutils.JSONObject) (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.GetString("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")
}
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 msg string
if len(resourceStr) == 0 {
msg = fmt.Sprintf("the %s in guest template is not a public resource", resourceName)
} else {
msg = fmt.Sprintf("the %s '%s' in guest template is not a public resource", resourceName, resourceStr)
}
if len(scope) > 0 {
msg += fmt.Sprintf(" in %s scope", scope)
}
return httperrors.NewForbiddenError(msg)
}
+6
View File
@@ -4877,6 +4877,12 @@ func (self *SGuestManager) checkGuestImage(ctx context.Context, input *api.Serve
return errors.Wrap(err, "get guest image from glance error")
}
// input.GuestImageID maybe name of guestimage
if ret.Contains("id") {
id, _ := ret.GetString("id")
input.GuestImageID = id
}
images := &api.SImagesInGuest{}
err = ret.Unmarshal(images)
if err != nil {
+136
View File
@@ -0,0 +1,136 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package models
import (
"context"
"database/sql"
"net/url"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
computeapis "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"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"
)
type SServiceCatalogManager struct {
db.SSharableVirtualResourceBaseManager
}
type SServiceCatalog struct {
db.SSharableVirtualResourceBase
IconUrl string `charset:"ascii" create:"optional" list:"user" get:"user"`
GuestTemplateID string `width:"128" charset:"ascii" create:"optional" list:"user" get:"user"`
}
var ServiceCatalogManager *SServiceCatalogManager
func init() {
ServiceCatalogManager = &SServiceCatalogManager{
SSharableVirtualResourceBaseManager: db.NewSharableVirtualResourceBaseManager(
SServiceCatalog{},
"servicecatalogs_tbl",
"servicecatalog",
"servicecatalogs",
),
}
ServiceCatalogManager.SetVirtualObject(ServiceCatalogManager)
}
func (scm *SServiceCatalogManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential,
ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input *computeapis.ServiceCatalogCreateInput) (*jsonutils.JSONDict,
error) {
if len(input.GuestTemplate) == 0 {
return nil, httperrors.NewMissingParameterError("guest_template")
}
model, err := GuestTemplateManager.FetchByIdOrName(userCred, input.GuestTemplate)
if errors.Cause(err) == sql.ErrNoRows {
return nil, httperrors.NewResourceNotFoundError("no such guest template")
}
if err != nil {
return nil, err
}
gt := model.(*SGuestTemplate)
//scope := rbacutils.String2Scope(gt.PublicScope)
//if !gt.IsPublic || scope != rbacutils.ScopeSystem {
// return nil, httperrors.NewForbiddenError("guest template must be public in scope system")
//}
if userCred.GetProjectId() != gt.ProjectId {
return nil, httperrors.NewForbiddenError("guest template must has same project id with the request")
}
data := input.JSON(input)
data.Remove("guest_template")
data.Add(jsonutils.NewString(model.GetId()), "guest_template_id")
// check url
if len(input.IconUrl) == 0 {
return data, nil
}
url, err := url.Parse(input.IconUrl)
if err != nil {
return nil, httperrors.NewInputParameterError("fail to parse icon url '%s'", input.IconUrl)
}
data.Add(jsonutils.NewString(url.String()), "icon_url")
return data, nil
}
func (sc *SServiceCatalog) AllowPerformDeploy(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return sc.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, sc, "deploy")
}
func (sc *SServiceCatalog) PerformDeploy(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, input *computeapis.ServiceCatalogDeploy) (jsonutils.JSONObject, error) {
if len(input.Name) == 0 && len(input.GenerateName) == 0 {
return nil, httperrors.NewMissingParameterError("name or generate_name")
}
model, err := GuestTemplateManager.FetchById(sc.GuestTemplateID)
if errors.Cause(err) == sql.ErrNoRows {
return nil, httperrors.NewResourceNotFoundError("no such guest_template %s", sc.GuestTemplateID)
}
if err != nil {
return nil, err
}
guestTempalte := model.(*SGuestTemplate)
content := guestTempalte.Content
contentDict := content.(*jsonutils.JSONDict)
if len(input.GenerateName) != 0 {
contentDict.Add(jsonutils.NewString(input.GenerateName), "generate_name")
} else {
contentDict.Add(jsonutils.NewString(input.Name), "name")
}
if input.Count != 0 {
input.Count = 1
}
contentDict.Add(jsonutils.NewInt(int64(input.Count)), "count")
s := auth.GetSession(ctx, userCred, options.Options.Region, "")
_, err = modules.Servers.Create(s, content)
if err != nil {
return nil, errors.Wrap(err, "fail to create guest")
}
return nil, err
}
+3
View File
@@ -137,6 +137,9 @@ func InitHandlers(app *appsrv.Application) {
models.ElasticcacheBackupManager,
models.ElasticcacheSkuManager,
models.GlobalNetworkManager,
models.GuestTemplateManager,
models.ServiceCatalogManager,
} {
db.RegisterModelManager(manager)
handler := db.NewModelHandler(manager)
+30
View File
@@ -0,0 +1,30 @@
// 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 modules
import "yunion.io/x/onecloud/pkg/mcclient/modulebase"
type SGuestTemplateManager struct {
modulebase.ResourceManager
}
var GuestTemplate SGuestTemplateManager
func init() {
GuestTemplate = SGuestTemplateManager{NewComputeManager("servertemplate", "servertemplates",
[]string{"ID", "Name", "Public_Scope", "Is_Public", "Project_Id", "Content"},
[]string{})}
registerCompute(&GuestTemplate)
}
@@ -0,0 +1,31 @@
// 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 modules
import "yunion.io/x/onecloud/pkg/mcclient/modulebase"
type SServiceCatalog struct {
modulebase.ResourceManager
}
var ServiceCatalog SServiceCatalog
func init() {
ServiceCatalog = SServiceCatalog{NewComputeManager("servicecatalog", "servicecatalogs",
[]string{"ID", "Name", "Public_Scope", "Is_Public", "Icon_Url", "Guest_Template_ID"},
[]string{})}
registerCompute(&ServiceCatalog)
}
+23 -7
View File
@@ -245,9 +245,14 @@ type ServerCreateFromInstanceSnapshot struct {
}
type ServerCreateOptions struct {
ServerCreateOptionalOptions
NAME string `help:"Name of server" json:"-"`
}
type ServerCreateOptionalOptions struct {
ServerConfigs
NAME string `help:"Name of server" json:"-"`
MemSpec string `help:"Memory size Or Instance Type" metavar:"MEMSPEC" json:"-"`
Keypair string `help:"SSH Keypair"`
@@ -340,7 +345,7 @@ func (o *ServerCreateOptions) ToScheduleInput() (*schedapi.ScheduleInput, error)
return input, nil
}
func (opts *ServerCreateOptions) Params() (*computeapi.ServerCreateInput, error) {
func (opts *ServerCreateOptionalOptions) OptionalParams() (*computeapi.ServerCreateInput, error) {
config, err := opts.ServerConfigs.Data()
if err != nil {
return nil, err
@@ -370,11 +375,6 @@ func (opts *ServerCreateOptions) Params() (*computeapi.ServerCreateInput, error)
Secgroups: opts.Secgroups,
}
if opts.GenerateName {
params.GenerateName = opts.NAME
} else {
params.Name = opts.NAME
}
if regutils.MatchSize(opts.MemSpec) {
memSize, err := fileutils.GetSizeMb(opts.MemSpec, 'M', 1024)
if err != nil {
@@ -429,6 +429,22 @@ func (opts *ServerCreateOptions) Params() (*computeapi.ServerCreateInput, error)
return params, nil
}
func (opts *ServerCreateOptions) Params() (*computeapi.ServerCreateInput, error) {
params, err := opts.OptionalParams()
if err != nil {
return nil, err
}
if opts.GenerateName {
params.GenerateName = opts.NAME
} else {
params.Name = opts.NAME
}
return params, nil
}
type ServerStopOptions struct {
ID []string `help:"ID or Name of server" json:"-"`
Force *bool `help:"Stop server forcefully" json:"is_force"`