Compare commits

...

20 Commits

Author SHA1 Message Date
Jian Qiu
c6d5c9ea0a fix: ensure s3 client init for s3 storage driver (#24319)
Co-authored-by: Qiu Jian <qiujian@yunionyun.com>
2026-02-11 22:08:19 +08:00
wanyaoqi
ecb65c2563 fix(host): set dmesg parse failed log level to debug (#24308) 2026-02-11 00:59:48 +08:00
屈轩
7dc2246c10 fix(notify): notify receiver (#24300) 2026-02-09 20:42:52 +08:00
wanyaoqi
0b02c49c7c fix(host): deploy nics config defore do add nics (#24275) 2026-02-09 08:38:16 +08:00
屈轩
5e565cf2be fix(region): vendor update (#24268) 2026-02-07 13:52:21 +08:00
wanyaoqi
aef2e3e956 fix(region,host): rename screen dump info structure (#24257) 2026-02-06 18:08:35 +08:00
屈轩
335cdf37b4 fix(region): snapshot policy operation (#24251) 2026-02-05 16:40:40 +08:00
wanyaoqi
43bb829edc fix(glance): set s3 bucket name before s3 init check (#24236) 2026-02-04 19:33:28 +08:00
屈轩
217496161a fix(yunionconf): support tags (#24234) 2026-02-04 17:14:12 +08:00
屈轩
f2d2495be3 fix(region): public cloud sku create (#24229) 2026-02-04 14:57:45 +08:00
屈轩
b333d023f9 fix(region): support cloudpods vm disk driver (#24224) 2026-02-04 14:28:21 +08:00
Zexi Li
29d1a9beb3 fix(region,climc): persist netinterface vlan_id to hostnetwork (#24214)
- climc: add VlanId to host-add-netif, host-remove-netif, host-enable-netif,
  host-disable-netif
- region: set bn.VlanId from netif.VlanId when attaching to network
- mcclient: include Vlan_ID in baremetalnetworks list columns
2026-02-04 11:03:59 +08:00
屈轩
8a70ad1fe1 fix(region): avoid duplicate check when update account aksk (#24208) 2026-02-03 20:53:06 +08:00
屈轩
55c139f4ab fix(region): init snapshot policy disk table (#24201) 2026-02-02 20:38:08 +08:00
wanyaoqi
24fdc3782f fix(host): guestnetworksync do nothing on detach vfio nic (#24177) 2026-02-02 19:02:59 +08:00
屈轩
69fce36cab fix(climc): sku create support arch (#24189) 2026-02-02 14:17:23 +08:00
屈轩
aef3f68fd5 fix(notify): support webhook sign (#24184) 2026-02-02 12:54:06 +08:00
wanyaoqi
67566abfbb fix(host): live migrate add timeout (#24164) 2026-02-01 20:24:53 +08:00
wanyaoqi
382ec17c04 fix(region): server add set network num queues (#24165) 2026-02-01 20:12:08 +08:00
wanyaoqi
28f83d8dac fix(glance): set glance s3 bucket name default onecloud-images (#24170) 2026-02-01 20:10:05 +08:00
54 changed files with 894 additions and 184 deletions

View File

@@ -42,32 +42,18 @@ import (
func init() {
cmd := shell.NewResourceCmd(&modules.Disks)
cmd.List(&compute_options.DiskListOptions{})
cmd.Show(&compute_options.DiskIdOptions{})
cmd.Perform("public", &compute_options.DiskIdOptions{})
cmd.Perform("private", &compute_options.DiskIdOptions{})
cmd.Perform("syncstatus", &compute_options.DiskIdOptions{})
cmd.Perform("change-owner-candidate-domains", &compute_options.DiskIdOptions{})
cmd.Perform("disk-cancel-delete", &compute_options.DiskIdOptions{})
cmd.Perform("set-class-metadata", &options.ResourceMetadataOptions{})
cmd.Perform("rebuild", &options.ResourceIdOptions{})
cmd.Perform("migrate", &compute_options.DiskMigrateOptions{})
cmd.Perform("change-billing-type", new(compute_options.DiskChangeBillingTypeOptions))
cmd.Perform("change-storage-type", &compute_options.DiskChangeStorageTypeOptions{})
type DiskDetailOptions struct {
ID string `help:"ID or Name of disk"`
}
R(&DiskDetailOptions{}, "disk-show", "Show details of disk", func(s *mcclient.ClientSession, args *DiskDetailOptions) error {
disk, e := modules.Disks.Get(s, args.ID, nil)
if e != nil {
return e
}
printObject(disk)
return nil
})
R(&DiskDetailOptions{}, "disk-cancel-delete", "Cancel pending delete disks", func(s *mcclient.ClientSession, args *DiskDetailOptions) error {
disk, e := modules.Disks.PerformAction(s, args.ID, "cancel-delete", nil)
if e != nil {
return e
}
printObject(disk)
return nil
})
type DiskDeleteOptions struct {
ID []string `help:"ID of disks to delete" metavar:"DISK"`
OverridePendingDelete bool `help:"Delete disk directly instead of pending delete" short-token:"f"`
@@ -96,25 +82,7 @@ func init() {
return nil
})
R(&DiskDetailOptions{}, "disk-public", "Make a disk public", func(s *mcclient.ClientSession, args *DiskDetailOptions) error {
disk, e := modules.Disks.PerformAction(s, args.ID, "public", nil)
if e != nil {
return e
}
printObject(disk)
return nil
})
R(&DiskDetailOptions{}, "disk-private", "Make a disk private", func(s *mcclient.ClientSession, args *DiskDetailOptions) error {
disk, e := modules.Disks.PerformAction(s, args.ID, "private", nil)
if e != nil {
return e
}
printObject(disk)
return nil
})
R(&DiskDetailOptions{}, "disk-metadata", "Get metadata of a disk", func(s *mcclient.ClientSession, args *DiskDetailOptions) error {
R(&compute_options.DiskIdOptions{}, "disk-metadata", "Get metadata of a disk", func(s *mcclient.ClientSession, args *compute_options.DiskIdOptions) error {
meta, e := modules.Disks.GetMetadata(s, args.ID, nil)
if e != nil {
return e
@@ -123,15 +91,6 @@ func init() {
return nil
})
R(&DiskDetailOptions{}, "disk-syncstatus", "Sync status for disk", func(s *mcclient.ClientSession, args *DiskDetailOptions) error {
ret, e := modules.Disks.PerformAction(s, args.ID, "syncstatus", nil)
if e != nil {
return e
}
printObject(ret)
return nil
})
type DiskUpdateOptions struct {
ID string `help:"ID or name of disk"`
Name string `help:"New name of disk"`
@@ -300,7 +259,7 @@ func init() {
return nil
})
R(&DiskDetailOptions{}, "disk-change-owner-candidate-domains", "Get change owner candidate domain list", func(s *mcclient.ClientSession, args *DiskDetailOptions) error {
R(&compute_options.DiskIdOptions{}, "disk-change-owner-candidate-domains", "Get change owner candidate domain list", func(s *mcclient.ClientSession, args *compute_options.DiskIdOptions) error {
result, err := modules.Disks.GetSpecific(s, args.ID, "change-owner-candidate-domains", nil)
if err != nil {
return err

View File

@@ -333,6 +333,7 @@ func init() {
IpAddr string `help:"IP address"`
Bridge string `help:"Bridge of hostwire"`
Interface string `help:"Interface name, eg:eth0, en0"`
VlanId int `help:"Vlan ID"`
}
R(&HostAddNetIfOptions{}, "host-add-netif", "Host add a NIC", func(s *mcclient.ClientSession, args *HostAddNetIfOptions) error {
params := jsonutils.NewDict()
@@ -352,6 +353,7 @@ func init() {
if len(args.Interface) > 0 {
params.Add(jsonutils.NewString(args.Interface), "interface")
}
addVlanIdToParams(params, args.VlanId)
result, err := modules.Hosts.PerformAction(s, args.ID, "add-netif", params)
if err != nil {
return err
@@ -361,12 +363,14 @@ func init() {
})
type HostRemoveNetIfOptions struct {
ID string `help:"ID or Name of host"`
MAC string `help:"MAC of NIC to remove"`
ID string `help:"ID or Name of host"`
MAC string `help:"MAC of NIC to remove"`
VlanId int `help:"Vlan Id"`
}
R(&HostRemoveNetIfOptions{}, "host-remove-netif", "Remove NIC from host", func(s *mcclient.ClientSession, args *HostRemoveNetIfOptions) error {
params := jsonutils.NewDict()
params.Add(jsonutils.NewString(args.MAC), "mac")
addVlanIdToParams(params, args.VlanId)
result, err := modules.Hosts.PerformAction(s, args.ID, "remove-netif", params)
if err != nil {
return err
@@ -381,6 +385,7 @@ func init() {
Ip string `help:"IP address"`
Network string `help:"network to connect"`
Reserved bool `help:"fetch IP from reserved pool"`
VlanId int `help:"Vlan ID"`
}
R(&HostEnableNetIfOptions{}, "host-enable-netif", "Enable a network interface for a host", func(s *mcclient.ClientSession, args *HostEnableNetIfOptions) error {
params := jsonutils.NewDict()
@@ -394,6 +399,7 @@ func init() {
if len(args.Network) > 0 {
params.Add(jsonutils.NewString(args.Network), "network")
}
addVlanIdToParams(params, args.VlanId)
result, err := modules.Hosts.PerformAction(s, args.ID, "enable-netif", params)
if err != nil {
return err
@@ -406,6 +412,7 @@ func init() {
ID string `help:"ID or Name of host"`
MAC string `help:"MAC of NIC to disable"`
Reserve bool `help:"Reserve the IP address"`
VlanId int `help:"Vlan Id"`
}
R(&HostDisableNetIfOptions{}, "host-disable-netif", "Disable a network interface", func(s *mcclient.ClientSession, args *HostDisableNetIfOptions) error {
params := jsonutils.NewDict()
@@ -413,6 +420,7 @@ func init() {
if args.Reserve {
params.Add(jsonutils.JSONTrue, "reserve")
}
addVlanIdToParams(params, args.VlanId)
result, err := modules.Hosts.PerformAction(s, args.ID, "disable-netif", params)
if err != nil {
return err
@@ -754,3 +762,9 @@ func init() {
},
)
}
func addVlanIdToParams(params *jsonutils.JSONDict, vlanId int) {
if vlanId > 1 {
params.Add(jsonutils.NewInt(int64(vlanId)), "vlan_id")
}
}

View File

@@ -37,7 +37,7 @@ func init() {
return nil
})
R(&options.TagListOptions{}, "tag-list", "List tags", func(s *mcclient.ClientSession, opts *options.TagListOptions) error {
R(&options.TagListOptions{}, "service-metadata-list", "List service metadata", func(s *mcclient.ClientSession, opts *options.TagListOptions) error {
var mod modulebase.IResourceManager
switch opts.Service {
case "compute":

View File

@@ -145,6 +145,7 @@ func init() {
cmd.Get("hardware-info", new(options.ServerIdOptions))
cmd.Get("screen-dump-show", new(options.ServerScreenDumpOptions))
cmd.BatchPerform("screen-dump", new(options.ServerIdsOptions))
cmd.Perform("set-network-num-queues", new(options.ServerSetNetworkNumQueues))
cmd.GetProperty(&options.ServerStatusStatisticsOptions{})
cmd.GetProperty(&options.ServerProjectStatisticsOptions{})

2
go.mod
View File

@@ -93,7 +93,7 @@ require (
k8s.io/client-go v0.19.3
k8s.io/cluster-bootstrap v0.19.3
moul.io/http2curl/v2 v2.3.0
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260123023413-f5d35910430c
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260207043119-2907d68518c5
yunion.io/x/executor v0.0.0-20250518005516-5402e9e0bed0
yunion.io/x/jsonutils v1.0.1-0.20250507052344-1abcf4f443b1
yunion.io/x/log v1.0.1-0.20240305175729-7cf2d6cd5a91

4
go.sum
View File

@@ -1272,8 +1272,8 @@ sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260123023413-f5d35910430c h1:x+vf8gAXmf1CYfMTKO7BAdXa1a8iMpffNUW0B3P3wNg=
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260123023413-f5d35910430c/go.mod h1:GdAwZ78fiqj13i2r1zNuE/D6YOlhh2q4jZjuyHiZTq8=
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260207043119-2907d68518c5 h1:CqO2GuJk/pT4yjI3OTeLm+/mY1H+aUK+B6hxhzIdUQ4=
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260207043119-2907d68518c5/go.mod h1:GdAwZ78fiqj13i2r1zNuE/D6YOlhh2q4jZjuyHiZTq8=
yunion.io/x/executor v0.0.0-20250518005516-5402e9e0bed0 h1:msG4SiDSVU7CrXH06WuHlNEZXIooTcmNbfrIGHuIHBU=
yunion.io/x/executor v0.0.0-20250518005516-5402e9e0bed0/go.mod h1:Uxuou9WQIeJXNpy7t2fPLL0BYLvLiMvGQwY7Qc6aSws=
yunion.io/x/jsonutils v0.0.0-20190625054549-a964e1e8a051/go.mod h1:4N0/RVzsYL3kH3WE/H1BjUQdFiWu50JGCFQuuy+Z634=

View File

@@ -87,6 +87,6 @@ type CachedimageListInput struct {
// valid cachedimage
Valid bool `json:"valid"`
// enum: [public, private]
// enum: ["public", "private"]
CloudEnv string `json:"cloud_env"`
}

View File

@@ -169,6 +169,8 @@ type DiskListInput struct {
// 根据是否绑定快照策略过滤
BindingSnapshotpolicy *bool `json:"binding_snapshotpolicy"`
// 根据是否磁盘所在虚拟机是否绑定主机快照策略
BindingServerSnapshotpolicy *bool `json:"binding_server_snapshotpolicy"`
}
type DiskResourceInput struct {
@@ -213,13 +215,22 @@ type SimpleGuest struct {
Bps int `json:"bps"`
// 计费类型
BillingType string `json:"billing_type"`
// 磁盘绑定的快照策略列表
Snapshotpolicy []SimpleSnapshotPolicy `json:"snapshotpolicy"`
}
type SimpleSnapshotPolicy struct {
Id string `json:"id"`
Name string `json:"name"`
RepeatWeekdays []int `json:"repeat_weekdays"`
TimePoints []int `json:"time_points"`
// 快照策略ID
Id string `json:"id"`
// 快照策略名称
Name string `json:"name"`
// 快照策略类型
ResourceType string `json:"resource_type"`
// 快照策略重复周期
RepeatWeekdays []int `json:"repeat_weekdays"`
// 快照策略时间点
TimePoints []int `json:"time_points"`
}
type DiskDetails struct {
@@ -239,11 +250,11 @@ type DiskDetails struct {
GuestStatus string `json:"guest_status"`
// 所挂载虚拟机计费类型
GuestBillingType string `json:"guest_billing_type"`
// 磁盘所在虚拟机绑定的主机快照策略数量
GuestSnapshotpolicyCount int `json:"guest_snapshotpolicy_count"`
// 自动清理时间
AutoDeleteAt time.Time `json:"auto_delete_at"`
// 自动快照策略状态
SnapshotpolicyStatus string `json:"snapshotpolicy_status,allowempty"`
// 自动快照策略
Snapshotpolicies []SimpleSnapshotPolicy `json:"snapshotpolicies"`
@@ -351,6 +362,7 @@ type DiskChagneStorageTypeInput struct {
}
type DiskSnapshotpolicyInput struct {
// 快照策略ID
SnapshotpolicyId string `json:"snapshotpolicy_id"`
}

View File

@@ -28,7 +28,7 @@ type SImagesInGuest struct {
DataImages []SSubImage
}
type SGuestScreenDump struct {
type SGuestScreenDumpInfo struct {
S3AccessKey string
S3SecretKey string
S3Endpoint string

View File

@@ -134,6 +134,14 @@ type ServerListInput struct {
// 根据是否绑定快照策略过滤
BindingSnapshotpolicy *bool `json:"binding_snapshotpolicy"`
// 根据虚机关联的磁盘是否绑定快照策略过滤
BindingDisksSnapshotpolicy *bool `json:"binding_disks_snapshotpolicy"`
}
// 主机快照策略绑定/设置接口入参
type ServerSnapshotpolicyInput struct {
// 快照策略ID
SnapshotpolicyId string `json:"snapshotpolicy_id"`
}
func (input *ServerListInput) AfterUnmarshal() {
@@ -183,6 +191,11 @@ type ServerDetails struct {
// 磁盘概要
Disks string `json:"disks"`
// 主机快照策略数量
SnapshotpolicyCount int `json:"snapshotpolicy_count"`
// 磁盘快照策略数量
DisksSnapshotpolicyCount int `json:"disks_snapshotpolicy_count"`
// 磁盘详情
DisksInfo []GuestDiskInfo `json:"disks_info"`
// 虚拟机Ip列表
@@ -712,6 +725,13 @@ func (input ServerDetachnetworkInput) IsForce() bool {
return input.Force != nil && *input.Force
}
type ServerSetNetworkNumQueuesInput struct {
// 虚机网卡 mac addr
MacAddr string `json:"mac_addr"`
// 网卡队列数
NumQueues int `json:"num_queues"`
}
type ServerMigrateForecastInput struct {
PreferHostId string `json:"prefer_host_id"`
// Deprecated

View File

@@ -162,6 +162,7 @@ type SendParams struct {
Header jsonutils.JSONObject
Body jsonutils.JSONObject
MsgKey string
SecretKey string `json:"secret_key"`
DomainId string
RemoteTemplateParam SRemoteTemplateParam
GroupKey string

View File

@@ -21,6 +21,10 @@ import (
"yunion.io/x/onecloud/pkg/apis"
)
const (
WEBHOOK_SIGNATURE_HEADER = "X-Auth-Token"
)
type RobotCreateInput struct {
apis.SharableVirtualResourceCreateInput
apis.EnabledBaseResourceCreateInput
@@ -37,6 +41,7 @@ type RobotCreateInput struct {
Header jsonutils.JSONObject `json:"header"`
Body jsonutils.JSONObject `json:"body"`
MsgKey string `json:"msg_key"`
SecretKey string `json:"secret_key"`
UseTemplate tristate.TriState `json:"use_template"`
}
@@ -63,8 +68,9 @@ type RobotUpdateInput struct {
Address string `json:"address"`
// description: Language preference
// example: en
Lang string `json:"lang"`
Header jsonutils.JSONObject `json:"header"`
Body jsonutils.JSONObject `json:"body"`
MsgKey string `json:"msg_key"`
Lang string `json:"lang"`
Header jsonutils.JSONObject `json:"header"`
Body jsonutils.JSONObject `json:"body"`
MsgKey string `json:"msg_key"`
SecretKey string `json:"secret_key"`
}

View File

@@ -17,7 +17,8 @@ package yunionconf
import "yunion.io/x/onecloud/pkg/apis"
const (
SERVICE_TYPE = apis.SERVICE_TYPE_YUNIONCONF
SERVICE_TYPE = apis.SERVICE_TYPE_YUNIONCONF
SERVICE_VERSION = ""
)
const (

View File

@@ -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 yunionconf
import "yunion.io/x/onecloud/pkg/apis"
type TagListInput struct {
apis.InfrasResourceBaseListInput
}
type TagDetails struct {
apis.InfrasResourceBaseDetails
}
type TagCreateInput struct {
apis.InfrasResourceBaseCreateInput
Values []string `json:"values"`
}

View File

@@ -169,7 +169,7 @@ type S3CommonOptions struct {
S3SecretKey string `help:"s3 secret key"`
S3Endpoint string `help:"s3 endpoint"`
S3UseSSL bool `help:"s3 access use ssl"`
S3BucketName string `help:"s3 bucket name" default:"onecloud-screendump"`
S3BucketName string `help:"s3 bucket name"`
S3BucketLifecycleKeepDay int `help:"s3 bucket lifecycle keep day" default:"180"`
}

View File

@@ -128,6 +128,10 @@ func (drv *SManagedVirtualizedGuestDriver) GetJsonDescAtHost(ctx context.Context
config.SysDisk.SizeGB = int(math.Ceil(float64(disk.DiskSize) / 1024))
config.SysDisk.Iops = disk.Iops
config.SysDisk.Throughput = disk.Throughput
if gds, err := disk.GetGuestDisk(); err == nil {
config.SysDisk.Driver = gds.Driver
config.SysDisk.CacheMode = gds.CacheMode
}
cache := storage.GetStoragecache()
imageId := disk.GetTemplateId()
//避免因同步过来的instance没有对应的imagecache信息重置密码时引发空指针访问
@@ -153,6 +157,10 @@ func (drv *SManagedVirtualizedGuestDriver) GetJsonDescAtHost(ctx context.Context
Throughput: disk.Throughput,
Name: disk.Name,
}
if gds, err := disk.GetGuestDisk(); err == nil {
dataDisk.Driver = gds.Driver
dataDisk.CacheMode = gds.CacheMode
}
config.DataDisks = append(config.DataDisks, dataDisk)
}
}

View File

@@ -765,7 +765,7 @@ func (acnt *SCloudaccount) PerformUpdateCredential(
}
changed := false
if len(account.Secret) > 0 || len(account.Account) > 0 {
if acnt.Account != account.Account && (len(account.Secret) > 0 || len(account.Account) > 0) {
// check duplication
q := acnt.GetModelManager().Query()
q = q.Equals("account", account.Account)

View File

@@ -218,6 +218,16 @@ func (manager *SDiskManager) ListItemFilter(
q = q.NotIn("id", spjsq)
}
}
if query.BindingServerSnapshotpolicy != nil {
guestDisks := GuestdiskManager.Query("disk_id")
sq := SnapshotPolicyResourceManager.Query("resource_id").Equals("resource_type", api.SNAPSHOT_POLICY_TYPE_SERVER).SubQuery()
gdsq := guestDisks.Join(sq, sqlchemy.Equals(guestDisks.Field("guest_id"), sq.Field("resource_id"))).SubQuery()
if *query.BindingServerSnapshotpolicy {
q = q.In("id", gdsq)
} else {
q = q.NotIn("id", gdsq)
}
}
guestId := query.ServerId
if len(guestId) > 0 {
@@ -383,11 +393,24 @@ func (manager *SDiskManager) QueryDistinctExtraFields(q *sqlchemy.SQuery, resour
return q, httperrors.ErrNotFound
}
func (self *SDisk) GetGuestDiskCount() (int, error) {
func (disk *SDisk) GetGuestDiskQuery() *sqlchemy.SQuery {
guestdisks := GuestdiskManager.Query()
guests := GuestManager.Query().SubQuery()
guestdisks = guestdisks.Join(guests, sqlchemy.Equals(guestdisks.Field("guest_id"), guests.Field("id")))
return guestdisks.Equals("disk_id", self.Id).CountWithError()
return guestdisks.Equals("disk_id", disk.Id)
}
func (self *SDisk) GetGuestDiskCount() (int, error) {
return self.GetGuestDiskQuery().CountWithError()
}
func (disk *SDisk) GetGuestDisk() (*SGuestdisk, error) {
guestdisk := &SGuestdisk{}
err := disk.GetGuestDiskQuery().First(guestdisk)
if err != nil {
return nil, errors.Wrap(err, "First")
}
return guestdisk, nil
}
func (self *SDisk) isAttached() (bool, error) {
@@ -2537,11 +2560,12 @@ func (manager *SDiskManager) FetchCustomizeColumns(
return rows
}
guests := map[string][]api.SimpleGuest{}
guests, guestIds := map[string][]api.SimpleGuest{}, []string{}
for _, guest := range guestInfo {
_, ok := guests[guest.DiskId]
if !ok {
guests[guest.DiskId] = []api.SimpleGuest{}
guestIds = append(guestIds, guest.Id)
}
guests[guest.DiskId] = append(guests[guest.DiskId], api.SimpleGuest{
Id: guest.Id,
@@ -2558,7 +2582,7 @@ func (manager *SDiskManager) FetchCustomizeColumns(
}
policySQ := SnapshotPolicyManager.Query().SubQuery()
dps := SnapshotPolicyResourceManager.Query().Equals("resource_type", api.SNAPSHOT_POLICY_TYPE_DISK).SubQuery()
dps := SnapshotPolicyResourceManager.Query().SubQuery()
q = policySQ.Query(
policySQ.Field("id"),
@@ -2566,8 +2590,9 @@ func (manager *SDiskManager) FetchCustomizeColumns(
policySQ.Field("time_points"),
policySQ.Field("repeat_weekdays"),
dps.Field("resource_id"),
dps.Field("resource_type"),
).Join(dps, sqlchemy.Equals(dps.Field("snapshotpolicy_id"), policySQ.Field("id"))).
Filter(sqlchemy.In(dps.Field("resource_id"), diskIds))
Filter(sqlchemy.OR(sqlchemy.In(dps.Field("resource_id"), diskIds), sqlchemy.In(dps.Field("resource_id"), guestIds)))
policyInfo := []struct {
Id string
@@ -2575,7 +2600,8 @@ func (manager *SDiskManager) FetchCustomizeColumns(
Status string
TimePoints []int
RepeatWeekdays []int
DiskId string
ResourceId string
ResourceType string
}{}
err = q.All(&policyInfo)
if err != nil {
@@ -2585,15 +2611,16 @@ func (manager *SDiskManager) FetchCustomizeColumns(
policies := map[string][]api.SimpleSnapshotPolicy{}
for _, policy := range policyInfo {
_, ok := policies[policy.DiskId]
_, ok := policies[policy.ResourceId]
if !ok {
policies[policy.DiskId] = []api.SimpleSnapshotPolicy{}
policies[policy.ResourceId] = []api.SimpleSnapshotPolicy{}
}
policies[policy.DiskId] = append(policies[policy.DiskId], api.SimpleSnapshotPolicy{
policies[policy.ResourceId] = append(policies[policy.ResourceId], api.SimpleSnapshotPolicy{
Id: policy.Id,
Name: policy.Name,
RepeatWeekdays: policy.RepeatWeekdays,
TimePoints: policy.TimePoints,
ResourceType: policy.ResourceType,
})
}
@@ -2601,12 +2628,15 @@ func (manager *SDiskManager) FetchCustomizeColumns(
rows[i].Guests, _ = guests[diskIds[i]]
names, status, billingTypes := []string{}, []string{}, []string{}
var iops, bps int
for _, guest := range rows[i].Guests {
for j := range rows[i].Guests {
guest := rows[i].Guests[j]
names = append(names, guest.Name)
status = append(status, guest.Status)
iops = guest.Iops
bps = guest.Bps
billingTypes = append(billingTypes, guest.BillingType)
rows[i].Guests[j].Snapshotpolicy, _ = policies[guest.Id]
rows[i].GuestSnapshotpolicyCount += len(rows[i].Guests[j].Snapshotpolicy)
}
rows[i].GuestCount = len(rows[i].Guests)
rows[i].Guest = strings.Join(names, ",")
@@ -3147,12 +3177,33 @@ func (disk *SDisk) GetUsages() []db.IUsage {
}
}
// 绑定磁盘快照策略
// 磁盘只能绑定一个快照策略,已绑定时报错
// 若磁盘所属主机已绑定主机快照策略,则磁盘不能再绑定快照策略
func (disk *SDisk) PerformBindSnapshotpolicy(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input *api.DiskSnapshotpolicyInput,
) (jsonutils.JSONObject, error) {
// 磁盘只能绑定一个快照策略,已绑定时报错
cnt, err := SnapshotPolicyResourceManager.GetBindingCount(disk.Id, api.SNAPSHOT_POLICY_TYPE_DISK)
if err != nil {
return nil, errors.Wrap(err, "GetBindingCount")
}
if cnt > 0 {
return nil, httperrors.NewConflictError("disk already bound to a snapshot policy")
}
// 若磁盘所属主机已绑定主机快照策略,则磁盘不能再绑定快照策略
if guest := disk.GetGuest(); guest != nil {
guestCnt, err := SnapshotPolicyResourceManager.GetBindingCount(guest.Id, api.SNAPSHOT_POLICY_TYPE_SERVER)
if err != nil {
return nil, errors.Wrap(err, "GetBindingCount for guest")
}
if guestCnt > 0 {
return nil, httperrors.NewConflictError("guest already has server snapshot policy, disk cannot bind snapshot policy")
}
}
spObj, err := validators.ValidateModel(ctx, userCred, SnapshotPolicyManager, &input.SnapshotpolicyId)
if err != nil {
return nil, err
@@ -3177,6 +3228,52 @@ func (disk *SDisk) PerformBindSnapshotpolicy(
return nil, sp.StartBindDisksTask(ctx, userCred, []string{disk.Id})
}
// 设置磁盘快照策略
// 可覆盖当前磁盘绑定的快照策略,若磁盘所属主机已绑定主机快照策略,则自动解除主机快照策略
func (disk *SDisk) PerformSetSnapshotpolicy(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
input *api.DiskSnapshotpolicyInput,
) (jsonutils.JSONObject, error) {
spObj, err := validators.ValidateModel(ctx, userCred, SnapshotPolicyManager, &input.SnapshotpolicyId)
if err != nil {
return nil, err
}
sp := spObj.(*SSnapshotPolicy)
if sp.Type != api.SNAPSHOT_POLICY_TYPE_DISK {
return nil, httperrors.NewBadRequestError("The snapshot policy %s is not a disk snapshot policy", sp.Name)
}
if len(sp.ManagerId) > 0 {
storage, err := disk.GetStorage()
if err != nil {
return nil, errors.Wrapf(err, "GetStorage")
}
if storage.ManagerId != sp.ManagerId {
return nil, httperrors.NewConflictError("The snapshot policy %s and disk account are different", sp.Name)
}
zone, err := storage.GetZone()
if err != nil {
return nil, errors.Wrapf(err, "GetZone")
}
if sp.CloudregionId != zone.CloudregionId {
return nil, httperrors.NewConflictError("The snapshot policy %s and the disk are in different region", sp.Name)
}
}
// 先解除当前绑定再绑定新策略
if err := SnapshotPolicyResourceManager.RemoveByResource(disk.Id, api.SNAPSHOT_POLICY_TYPE_DISK); err != nil {
return nil, errors.Wrap(err, "RemoveByResource")
}
// 若磁盘所属主机已绑定主机快照策略,则磁盘不能再绑定快照策略
if guest := disk.GetGuest(); guest != nil {
if err := SnapshotPolicyResourceManager.RemoveByResource(guest.Id, api.SNAPSHOT_POLICY_TYPE_SERVER); err != nil {
return nil, errors.Wrap(err, "RemoveByResource")
}
}
return nil, sp.StartBindDisksTask(ctx, userCred, []string{disk.Id})
}
// 解绑自动快照策略
func (disk *SDisk) PerformUnbindSnapshotpolicy(
ctx context.Context,
userCred mcclient.TokenCredential,

View File

@@ -143,7 +143,7 @@ func (self *SGuest) PerformEvent(ctx context.Context, userCred mcclient.TokenCre
kwargs := jsonutils.NewDict()
kwargs.Set("reason", jsonutils.NewString(event))
if data.Contains("screen_dump_info") {
screenDumpInfo := api.SGuestScreenDump{}
screenDumpInfo := api.SGuestScreenDumpInfo{}
if err := data.Unmarshal(&screenDumpInfo, "screen_dump_info"); err != nil {
log.Errorf("failed unmarshal screen_dump_info %s", err)
} else {
@@ -4255,6 +4255,29 @@ func (self *SGuest) SaveRenewInfo(
return nil
}
func (self *SGuest) PerformSetNetworkNumQueues(
ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ServerSetNetworkNumQueuesInput,
) (jsonutils.JSONObject, error) {
if self.Status != api.VM_READY {
return nil, httperrors.NewInvalidStatusError("can't set network num_queues on vm %s", self.Status)
}
if input.NumQueues < 1 {
return nil, httperrors.NewInputParameterError("invalid num_queues %d", input.NumQueues)
}
gn, err := self.GetGuestnetworkByMac(input.MacAddr)
if err != nil {
if errors.Cause(err) == sql.ErrNoRows {
return nil, httperrors.NewNotFoundError("guest network mac %s not found", input.MacAddr)
}
}
_, err = db.Update(gn, func() error {
gn.NumQueues = input.NumQueues
return nil
})
return nil, err
}
func (self *SGuest) PerformStreamDisksComplete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
disks, err := self.GetDisks()
if err != nil {
@@ -5506,6 +5529,96 @@ func (self *SGuest) PerformUnbindGroups(ctx context.Context, userCred mcclient.T
return nil, nil
}
// 绑定主机快照策略
// 主机只能绑定一个快照策略,已绑定时报错
// 若主机下任意磁盘已绑定快照策略则报错
func (self *SGuest) PerformBindSnapshotpolicy(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, input *api.ServerSnapshotpolicyInput) (jsonutils.JSONObject, error) {
if len(input.SnapshotpolicyId) == 0 {
return nil, httperrors.NewMissingParameterError("snapshotpolicy_id")
}
spObj, err := validators.ValidateModel(ctx, userCred, SnapshotPolicyManager, &input.SnapshotpolicyId)
if err != nil {
return nil, err
}
sp := spObj.(*SSnapshotPolicy)
if sp.Type != api.SNAPSHOT_POLICY_TYPE_SERVER {
return nil, httperrors.NewBadRequestError("The snapshot policy %s is not a server snapshot policy", sp.Name)
}
// 主机只能绑定一个快照策略
cnt, err := SnapshotPolicyResourceManager.GetBindingCount(self.Id, api.SNAPSHOT_POLICY_TYPE_SERVER)
if err != nil {
return nil, errors.Wrap(err, "GetBindingCount")
}
if cnt > 0 {
return nil, httperrors.NewConflictError("guest already bound to a snapshot policy")
}
// 若主机下任意磁盘已绑定快照策略,则主机不能再绑定主机快照策略
disks, err := self.GetDisks()
if err != nil {
return nil, errors.Wrap(err, "GetDisks")
}
for _, d := range disks {
diskCnt, err := SnapshotPolicyResourceManager.GetBindingCount(d.Id, api.SNAPSHOT_POLICY_TYPE_DISK)
if err != nil {
return nil, errors.Wrap(err, "GetBindingCount for disk")
}
if diskCnt > 0 {
return nil, httperrors.NewConflictError("guest has disk %s bound to snapshot policy, guest cannot bind server snapshot policy", d.Name)
}
}
sr := &SSnapshotPolicyResource{}
sr.SetModelManager(SnapshotPolicyResourceManager, sr)
sr.SnapshotpolicyId = sp.Id
sr.ResourceId = self.Id
sr.ResourceType = api.SNAPSHOT_POLICY_TYPE_SERVER
if err := SnapshotPolicyResourceManager.TableSpec().Insert(ctx, sr); err != nil {
return nil, errors.Wrap(err, "Insert")
}
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_BIND, input, userCred, true)
return nil, nil
}
// 设置主机快照策略
// 可覆盖当前主机绑定的快照策略,若主机下任意磁盘已绑定快照策略,则自动解除磁盘快照策略
func (self *SGuest) PerformSetSnapshotpolicy(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, input *api.ServerSnapshotpolicyInput) (jsonutils.JSONObject, error) {
if len(input.SnapshotpolicyId) == 0 {
return nil, httperrors.NewMissingParameterError("snapshotpolicy_id")
}
spObj, err := validators.ValidateModel(ctx, userCred, SnapshotPolicyManager, &input.SnapshotpolicyId)
if err != nil {
return nil, err
}
sp := spObj.(*SSnapshotPolicy)
if sp.Type != api.SNAPSHOT_POLICY_TYPE_SERVER {
return nil, httperrors.NewBadRequestError("The snapshot policy %s is not a server snapshot policy", sp.Name)
}
if err := SnapshotPolicyResourceManager.RemoveByResource(self.Id, api.SNAPSHOT_POLICY_TYPE_SERVER); err != nil {
return nil, errors.Wrap(err, "RemoveByResource")
}
// 若主机下任意磁盘已绑定快照策略,则主机不能再绑定主机快照策略
disks, err := self.GetDisks()
if err != nil {
return nil, errors.Wrap(err, "GetDisks")
}
for _, d := range disks {
if err := SnapshotPolicyResourceManager.RemoveByResource(d.Id, api.SNAPSHOT_POLICY_TYPE_DISK); err != nil {
return nil, errors.Wrap(err, "RemoveByResource")
}
}
sr := &SSnapshotPolicyResource{}
sr.SetModelManager(SnapshotPolicyResourceManager, sr)
sr.SnapshotpolicyId = sp.Id
sr.ResourceId = self.Id
sr.ResourceType = api.SNAPSHOT_POLICY_TYPE_SERVER
if err := SnapshotPolicyResourceManager.TableSpec().Insert(ctx, sr); err != nil {
return nil, errors.Wrap(err, "Insert")
}
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_UPDATE, input, userCred, true)
return nil, nil
}
func (self *SGuest) checkGroups(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject,
data jsonutils.JSONObject) (sets.String, error) {

View File

@@ -90,6 +90,15 @@ func (manager *SGuestManager) FetchCustomizeColumns(
}
}
}
if len(fields) == 0 || fields.Contains("snapshotpolicy") {
counts := fetchGuestSnapshotpolicyInfo(guestIds)
for i := range rows {
rows[i].SnapshotpolicyCount = counts[guestIds[i]]
for j := range rows[i].DisksInfo {
rows[i].DisksSnapshotpolicyCount += counts[rows[i].DisksInfo[j].Id]
}
}
}
/*if len(fields) == 0 || fields.Contains("ips") {
gips := fetchGuestIPs(guestIds, tristate.False)
if gips != nil {
@@ -440,6 +449,34 @@ func fetchGuestDisksInfo(guestIds []string) map[string][]GuestDiskInfo {
return ret
}
func fetchGuestSnapshotpolicyInfo(guestIds []string) map[string]int {
ret := map[string]int{}
disks := GuestdiskManager.Query("disk_id").In("guest_id", guestIds).SubQuery()
spq := SnapshotPolicyResourceManager.Query()
spq = spq.Filter(sqlchemy.OR(
sqlchemy.In(spq.Field("resource_id"), guestIds),
sqlchemy.In(spq.Field("resource_id"), disks),
))
sq := spq.SubQuery()
q := sq.Query(
sq.Field("resource_id"),
sqlchemy.COUNT("count", sq.Field("snapshotpolicy_id")),
).GroupBy(sq.Field("resource_id"))
counts := []struct {
ResourceId string
Count int
}{}
err := q.All(&counts)
if err != nil {
return nil
}
for _, count := range counts {
ret[count.ResourceId] = count.Count
}
return ret
}
func (guest *SGuest) GetDisksSize() int {
return guest.getDiskSize()
}

View File

@@ -738,6 +738,16 @@ func (manager *SGuestManager) ListItemFilter(
q = q.NotIn("id", spjsq)
}
}
if query.BindingDisksSnapshotpolicy != nil {
guestDisks := GuestdiskManager.Query("guest_id")
sq := SnapshotPolicyResourceManager.Query("resource_id").Equals("resource_type", api.SNAPSHOT_POLICY_TYPE_DISK).SubQuery()
gdsq := guestDisks.Join(sq, sqlchemy.Equals(guestDisks.Field("disk_id"), sq.Field("resource_id"))).SubQuery()
if *query.BindingDisksSnapshotpolicy {
q = q.In("id", gdsq)
} else {
q = q.NotIn("id", gdsq)
}
}
return q, nil
}

View File

@@ -77,7 +77,7 @@ func (self *SGuestScreenDump) Delete(ctx context.Context, userCred mcclient.Toke
return db.DeleteModel(ctx, userCred, self)
}
func (self *SGuest) SaveGuestScreenDump(ctx context.Context, userCred mcclient.TokenCredential, screenDumpInfo *api.SGuestScreenDump) (*SGuestScreenDump, error) {
func (self *SGuest) SaveGuestScreenDump(ctx context.Context, userCred mcclient.TokenCredential, screenDumpInfo *api.SGuestScreenDumpInfo) (*SGuestScreenDump, error) {
sd := new(SGuestScreenDump)
sd.SetModelManager(GuestScreenDumpManager, sd)
sd.GuestId = self.GetId()

View File

@@ -5876,6 +5876,7 @@ func (hh *SHost) Attach2Network(
bn.NetworkId = net.Id
bn.IpAddr = freeIp
bn.MacAddr = netif.Mac
bn.VlanId = netif.VlanId
err = HostnetworkManager.TableSpec().Insert(ctx, bn)
if err != nil {
return nil, errors.Wrap(err, "HostnetworkManager.TableSpec().Insert")

View File

@@ -320,22 +320,6 @@ func (self *SServerSkuManager) ValidateCreateData(ctx context.Context, userCred
region, _ = zone.GetRegion()
}
if input.CpuCoreCount < 1 || input.CpuCoreCount > options.Options.SkuMaxCpuCount {
return input, httperrors.NewOutOfRangeError("cpu_core_count should be range of 1~%d", options.Options.SkuMaxCpuCount)
}
if input.MemorySizeMB < 512 || input.MemorySizeMB > 1024*options.Options.SkuMaxMemSize {
return input, httperrors.NewOutOfRangeError("memory_size_mb, shoud be range of 512~%d", 1024*options.Options.SkuMaxMemSize)
}
if len(input.InstanceTypeCategory) == 0 {
input.InstanceTypeCategory = api.SkuCategoryGeneralPurpose
}
if !utils.IsInStringArray(input.InstanceTypeCategory, api.SKU_FAMILIES) {
return input, httperrors.NewInputParameterError("instance_type_category shoud be one of %s", api.SKU_FAMILIES)
}
if input.Enabled == nil {
enabled := true
input.Enabled = &enabled
@@ -346,14 +330,41 @@ func (self *SServerSkuManager) ValidateCreateData(ctx context.Context, userCred
if region != nil {
input.Provider = region.Provider
}
if input.Provider == api.CLOUD_PROVIDER_ONECLOUD {
input.CloudregionId = api.DEFAULT_REGION_ID
} else if utils.IsInStringArray(input.Provider, api.PRIVATE_CLOUD_PROVIDERS) {
input.Status = api.SkuStatusCreating
}
input.LocalCategory = input.InstanceTypeCategory
input.InstanceTypeFamily = api.InstanceFamilies[input.InstanceTypeCategory]
if !utils.IsInStringArray(input.Provider, api.PUBLIC_CLOUD_PROVIDERS) {
if input.CpuCoreCount < 1 || input.CpuCoreCount > options.Options.SkuMaxCpuCount {
return input, httperrors.NewOutOfRangeError("cpu_core_count should be range of 1~%d", options.Options.SkuMaxCpuCount)
}
if input.MemorySizeMB < 512 || input.MemorySizeMB > 1024*options.Options.SkuMaxMemSize {
return input, httperrors.NewOutOfRangeError("memory_size_mb, shoud be range of 512~%d", 1024*options.Options.SkuMaxMemSize)
}
if len(input.InstanceTypeCategory) == 0 {
input.InstanceTypeCategory = api.SkuCategoryGeneralPurpose
}
if !utils.IsInStringArray(input.InstanceTypeCategory, api.SKU_FAMILIES) {
return input, httperrors.NewInputParameterError("instance_type_category shoud be one of %s", api.SKU_FAMILIES)
}
input.LocalCategory = input.InstanceTypeCategory
input.InstanceTypeFamily = api.InstanceFamilies[input.InstanceTypeCategory]
}
if len(input.LocalCategory) == 0 {
input.LocalCategory = input.InstanceTypeCategory
}
if len(input.InstanceTypeFamily) == 0 {
input.InstanceTypeFamily = api.InstanceFamilies[input.InstanceTypeCategory]
}
var err error
if len(input.Name) == 0 {

View File

@@ -91,3 +91,8 @@ func (man *SSnapshotPolicyResourceManager) RemoveBySnapshotpolicy(id string) err
)
return err
}
// GetBindingCount returns the number of snapshot policies bound to the given resource.
func (man *SSnapshotPolicyResourceManager) GetBindingCount(resourceId, resourceType string) (int, error) {
return man.Query().Equals("resource_id", resourceId).Equals("resource_type", resourceType).CountWithError()
}

View File

@@ -510,6 +510,12 @@ func (sp *SSnapshotPolicy) PerformBindDisks(
return nil, sp.StartBindDisksTask(ctx, userCred, diskIds)
}
// 绑定资源
// 目前仅支持绑定主机和磁盘
// 磁盘只能绑定一个快照策略,已绑定时报错
// 若磁盘所属主机已绑定主机快照策略,则磁盘不能再绑定快照策略
// 主机只能绑定一个快照策略,已绑定时报错
// 若主机下任意磁盘已绑定快照策略,则主机不能再绑定主机快照策略
func (sp *SSnapshotPolicy) PerformBindResources(
ctx context.Context,
userCred mcclient.TokenCredential,
@@ -522,15 +528,63 @@ func (sp *SSnapshotPolicy) PerformBindResources(
for i := range input.Resources {
switch input.Resources[i].Type {
case api.SNAPSHOT_POLICY_TYPE_DISK:
_, err := validators.ValidateModel(ctx, userCred, DiskManager, &input.Resources[i].Id)
if sp.Type != api.SNAPSHOT_POLICY_TYPE_DISK {
return nil, httperrors.NewBadRequestError("The snapshot policy %s is not a disk snapshot policy", sp.Name)
}
diskObj, err := validators.ValidateModel(ctx, userCred, DiskManager, &input.Resources[i].Id)
if err != nil {
return nil, err
}
disk := diskObj.(*SDisk)
// 磁盘只能绑定一个快照策略
cnt, err := SnapshotPolicyResourceManager.GetBindingCount(disk.Id, api.SNAPSHOT_POLICY_TYPE_DISK)
if err != nil {
return nil, errors.Wrap(err, "GetBindingCount")
}
if cnt > 0 {
return nil, httperrors.NewConflictError("disk %s already bound to a snapshot policy", disk.Name)
}
// 若磁盘所属主机已绑定主机快照策略,则磁盘不能再绑定
if guest := disk.GetGuest(); guest != nil {
guestCnt, err := SnapshotPolicyResourceManager.GetBindingCount(guest.Id, api.SNAPSHOT_POLICY_TYPE_SERVER)
if err != nil {
return nil, errors.Wrap(err, "GetBindingCount for guest")
}
if guestCnt > 0 {
return nil, httperrors.NewConflictError("guest %s already has server snapshot policy, disk cannot bind snapshot policy", guest.Name)
}
}
case api.SNAPSHOT_POLICY_TYPE_SERVER:
_, err := validators.ValidateModel(ctx, userCred, GuestManager, &input.Resources[i].Id)
if sp.Type != api.SNAPSHOT_POLICY_TYPE_SERVER {
return nil, httperrors.NewBadRequestError("The snapshot policy %s is not a server snapshot policy", sp.Name)
}
guestObj, err := validators.ValidateModel(ctx, userCred, GuestManager, &input.Resources[i].Id)
if err != nil {
return nil, err
}
guest := guestObj.(*SGuest)
// 主机只能绑定一个快照策略
cnt, err := SnapshotPolicyResourceManager.GetBindingCount(guest.Id, api.SNAPSHOT_POLICY_TYPE_SERVER)
if err != nil {
return nil, errors.Wrap(err, "GetBindingCount")
}
if cnt > 0 {
return nil, httperrors.NewConflictError("guest %s already bound to a snapshot policy", guest.Name)
}
// 若主机下任意磁盘已绑定快照策略,则主机不能再绑定主机快照策略
disks, err := guest.GetDisks()
if err != nil {
return nil, errors.Wrap(err, "guest.GetDisks")
}
for _, d := range disks {
diskCnt, err := SnapshotPolicyResourceManager.GetBindingCount(d.Id, api.SNAPSHOT_POLICY_TYPE_DISK)
if err != nil {
return nil, errors.Wrap(err, "GetBindingCount for disk")
}
if diskCnt > 0 {
return nil, httperrors.NewConflictError("guest %s has disk %s bound to snapshot policy, guest cannot bind server snapshot policy", guest.Name, d.Name)
}
}
default:
return nil, httperrors.NewBadRequestError("Invalid resource type: %s", input.Resources[i].Type)
}

View File

@@ -101,6 +101,8 @@ func InitHandlers(app *appsrv.Application) {
models.WafRuleStatementManager,
models.LoadbalancerSecurityGroupManager,
models.SnapshotPolicyDiskManager,
} {
db.RegisterModelManager(manager)
}

View File

@@ -653,6 +653,12 @@ func (n *SGuestNetworkSyncTask) Start(callback func(...error)) {
}
n.addNicMacs = addNicMacs
n.addNicConfs = addNicConfs
// deploy nics configure before do add nics
allNics := append(n.guest.Desc.Nics, n.addNics...)
if err := n.guest.QgaDeployNicsConfigure(allNics); err != nil {
log.Errorf("failed do QgaDeployNicsConfigure %s", err)
}
}
n.delNicCnt = len(n.delNics)
@@ -671,6 +677,7 @@ func (n *SGuestNetworkSyncTask) syncNetworkConf() {
} else {
func() {
if len(n.addNicMacs) > 0 || n.delNicCnt > 0 {
// redeploy nics config after add/del nics
if err := n.guest.QgaDeployNicsConfigure(n.guest.Desc.Nics); err != nil {
log.Errorf("failed do QgaDeployNicsConfigure %s", err)
return
@@ -678,7 +685,7 @@ func (n *SGuestNetworkSyncTask) syncNetworkConf() {
}
if len(n.addNicMacs) > 0 {
// try restart added nics, wait for added nic ready
time.Sleep(3 * time.Second)
time.Sleep(6 * time.Second)
if err := n.qgaRestartAddedNics(); err != nil {
log.Errorf("failed qgaRestartAddedNics %s", err)
return
@@ -732,6 +739,11 @@ func (n *SGuestNetworkSyncTask) qgaGetAddedNicDevs() error {
}
func (n *SGuestNetworkSyncTask) removeNic(nic *desc.SGuestNetwork) {
if nic.Driver == "vfio-pci" {
n.onDeviceDel(nic)
return
}
callback := func(res string) {
if len(res) > 0 && !strings.Contains(res, "not found") {
log.Errorf("netdev del failed %s", res)
@@ -754,30 +766,34 @@ func (n *SGuestNetworkSyncTask) onNetdevDel(nic *desc.SGuestNetwork) {
n.delNicDevice(nic)
}
func (n *SGuestNetworkSyncTask) onDeviceDel(nic *desc.SGuestNetwork) {
var i = 0
for ; i < len(n.guest.Desc.Nics); i++ {
if n.guest.Desc.Nics[i].Index == nic.Index {
if nic.Pci != nil {
err := n.guest.pciAddrs.ReleasePCIAddress(nic.Pci.PCIAddr)
if err != nil {
log.Errorf("failed release nic pci addr %s", nic.Pci.PCIAddr)
}
}
break
}
}
if i < len(n.guest.Desc.Nics) {
n.guest.Desc.Nics = append(n.guest.Desc.Nics[:i], n.guest.Desc.Nics[i+1:]...)
}
n.syncNetworkConf()
}
func (n *SGuestNetworkSyncTask) delNicDevice(nic *desc.SGuestNetwork) {
callback := func(res string) {
if len(res) > 0 {
log.Errorf("network device del failed %s", res)
n.errors = append(n.errors, fmt.Errorf("network device del failed %s", res))
} else {
var i = 0
for ; i < len(n.guest.Desc.Nics); i++ {
if n.guest.Desc.Nics[i].Index == nic.Index {
if nic.Pci != nil {
err := n.guest.pciAddrs.ReleasePCIAddress(nic.Pci.PCIAddr)
if err != nil {
log.Errorf("failed release nic pci addr %s", nic.Pci.PCIAddr)
}
}
break
}
}
if i < len(n.guest.Desc.Nics) {
n.guest.Desc.Nics = append(n.guest.Desc.Nics[:i], n.guest.Desc.Nics[i+1:]...)
}
n.syncNetworkConf()
n.onDeviceDel(nic)
}
}
n.guest.Monitor.DeviceDel(fmt.Sprintf("netdev-%s", nic.Ifname), callback)
@@ -1169,7 +1185,22 @@ func (s *SGuestLiveMigrateTask) onSetAutoConverge(res string) {
return
}
// https://wiki.qemu.org/Features/AutoconvergeLiveMigration
s.Monitor.MigrateSetParameter("cpu-throttle-initial", options.HostOptions.LiveMigrateCpuThrottleInitial, s.onSetCpuThrottleInitial)
}
func (s *SGuestLiveMigrateTask) onSetCpuThrottleInitial(res string) {
if strings.Contains(strings.ToLower(res), "error") {
s.migrateFailed(fmt.Sprintf("Migrate set params cpu-throttle-initial error: %s", res))
return
}
s.Monitor.MigrateSetParameter("cpu-throttle-increment", options.HostOptions.LiveMigrateCpuThrottleIncrement, s.onSetCpuThrottleIncrement)
}
func (s *SGuestLiveMigrateTask) onSetCpuThrottleIncrement(res string) {
if strings.Contains(strings.ToLower(res), "error") {
s.migrateFailed(fmt.Sprintf("Migrate set params cpu-throttle-increment error: %s", res))
return
}
s.Monitor.MigrateSetCapability("events", "on", s.onMigrateEnableEvents)
}
@@ -1355,6 +1386,7 @@ func (s *SGuestLiveMigrateTask) startMigrateStatusCheck(res string) {
s.migrateFailed(fmt.Sprintf("Migrate error: %s", res))
return
}
s.startRamMigrateTimeout()
s.c = make(chan struct{})
for s.c != nil {
@@ -1401,6 +1433,14 @@ func (s *SGuestLiveMigrateTask) onGetMigrateStatus(stats *monitor.MigrationInfo)
} else if status == "cancelled" {
s.migrateFailed(status)
} else if status == "active" {
if !s.doTimeoutMigrate && s.timeoutAt.Before(time.Now()) {
s.Monitor.SimpleCommand("stop", s.onMigrateStartPostcopy)
s.doTimeoutMigrate = true
}
if s.doTimeoutMigrate {
return
}
var (
ramRemain int64
mbps float64

View File

@@ -1242,7 +1242,7 @@ func (s *SKVMGuestInstance) eventGuestPaniced(event *monitor.Event) {
if err != nil {
log.Errorf("faild put screenDumpPath %s to s3 %s", screenDumpPath, err)
} else {
screenDumpInfo := api.SGuestScreenDump{
screenDumpInfo := api.SGuestScreenDumpInfo{
S3AccessKey: options.HostOptions.S3AccessKey,
S3SecretKey: options.HostOptions.S3SecretKey,
S3Endpoint: options.HostOptions.S3Endpoint,

View File

@@ -166,6 +166,8 @@ func (host *SHostService) initHandlers(app *appsrv.Application) {
app_common.ExportOptionsHandler(app, &options.HostOptions)
}
const DEFAULT_SCREENDUMP_S3_BUCKET = "onecloud-screendump-new"
func initS3() {
url := options.HostOptions.S3Endpoint
if len(url) == 0 {
@@ -179,6 +181,10 @@ func initS3() {
}
url = prefix + url
}
if options.HostOptions.S3BucketName == "" {
options.HostOptions.S3BucketName = DEFAULT_SCREENDUMP_S3_BUCKET
}
err := s3.Init(
url,
options.HostOptions.S3AccessKey,

View File

@@ -104,7 +104,7 @@ func (c *SHostDmesgCollector) Start() {
entry, err := c.parseKmsgLine(line, bootTime)
if err != nil {
log.Errorf("failed parse kmsg line %s: %s", line, err)
log.Debugf("failed parse kmsg line %s: %s", line, err)
continue
}
if entry.Seq <= lastSeq {

View File

@@ -379,6 +379,9 @@ type GuestMetrics struct {
func (d *GuestMetrics) toTelegrafData(tags map[string]string) []string {
var tagArr = []string{}
for k, v := range tags {
if len(k) == 0 || len(v) == 0 {
continue
}
tagArr = append(tagArr, fmt.Sprintf("%s=%s", k, strings.ReplaceAll(v, " ", "+")))
}
tagStr := strings.Join(tagArr, ",")
@@ -386,6 +389,9 @@ func (d *GuestMetrics) toTelegrafData(tags map[string]string) []string {
mapToStatStr := func(m map[string]interface{}) string {
var statArr = []string{}
for k, v := range m {
if vs, ok := v.(string); ok && len(vs) == 0 {
continue
}
statArr = append(statArr, fmt.Sprintf("%s=%v", k, v))
}
return strings.Join(statArr, ",")
@@ -398,6 +404,9 @@ func (d *GuestMetrics) toTelegrafData(tags map[string]string) []string {
for i := range d.VmNetio {
netTagMap := d.VmNetio[i].ToTag()
for k, v := range netTagMap {
if len(k) == 0 || len(v) == 0 {
continue
}
tagStr = fmt.Sprintf("%s,%s=%s", tagStr, k, v)
}
res = append(res, fmt.Sprintf("%s,%s %s", "vm_netio", tagStr, mapToStatStr(d.VmNetio[i].ToMap())))

View File

@@ -32,8 +32,10 @@ type SHostBaseOptions struct {
DisableSecurityGroup bool `help:"disable security group" default:"false"`
HostCpuPassthrough bool `default:"true" help:"if it is true, set qemu cpu type as -cpu host, otherwise, qemu64. default is true"`
LiveMigrateCpuThrottleMax int64 `default:"99" help:"live migrate auto converge cpu throttle max"`
HostCpuPassthrough bool `default:"true" help:"if it is true, set qemu cpu type as -cpu host, otherwise, qemu64. default is true"`
LiveMigrateCpuThrottleMax int64 `default:"99" help:"live migrate auto converge cpu throttle max"`
LiveMigrateCpuThrottleInitial int64 `default:"60" help:"live migrate auto convert cpu throttle initial"`
LiveMigrateCpuThrottleIncrement int64 `default:"20" help:"live migrate auto convert cpu throttle increment"`
DefaultQemuVersion string `help:"Default qemu version" default:"4.2.0"`
NoHpet bool `help:"Disable qemu hpet timer" default:"true"`

View File

@@ -15,12 +15,16 @@
package options
import (
"strings"
"yunion.io/x/log"
common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
"yunion.io/x/onecloud/pkg/cloudcommon/pending_delete"
)
type SImageOptions struct {
common_options.HostCommonOptions
common_options.HostCommonOptions `"s3_bucket_name->default":"onecloud-images" "s3_bucket_lifecycle_keep_day->default":"0"`
common_options.DBOptions
@@ -81,5 +85,22 @@ func OnOptionsChange(oldO, newO interface{}) bool {
}
func (opt SImageOptions) HasValidS3Options() bool {
return len(opt.S3Endpoint) > 0 && len(opt.S3AccessKey) > 0 && len(opt.S3SecretKey) > 0 && len(opt.S3BucketName) > 0
msg := []string{}
if len(opt.S3Endpoint) <= 0 {
msg = append(msg, "s3_endpoint is required")
}
if len(opt.S3AccessKey) <= 0 {
msg = append(msg, "s3_access_key is required")
}
if len(opt.S3SecretKey) <= 0 {
msg = append(msg, "s3_secret_key is required")
}
if len(opt.S3BucketName) <= 0 {
msg = append(msg, "s3_bucket_name is required")
}
if len(msg) > 0 {
log.Errorf("invalid s3 options: %s", strings.Join(msg, ", "))
return false
}
return true
}

View File

@@ -140,8 +140,18 @@ func StartService() {
}
go func() {
if options.Options.S3BucketName == "" {
options.Options.S3BucketName = DEFAULT_IMAGE_S3_BUCKET
log.Infof("Set s3 bucket name to %s", options.Options.S3BucketName)
}
if options.Options.HasValidS3Options() {
initS3()
log.Infof("init s3 client success")
} else if options.Options.StorageDriver == api.IMAGE_STORAGE_DRIVER_S3 {
log.Fatalf("storage driver is s3, but s3 options are not valid")
} else {
log.Infof("storage driver is not s3 and no valid s3 options, skip init s3 client")
}
// check image after s3 mounted
models.CheckImages(app.GetContext())
@@ -188,6 +198,8 @@ func hasVmwareAccount() (bool, error) {
return res.Total > 0, nil
}
const DEFAULT_IMAGE_S3_BUCKET = "onecloud-images"
func initS3() {
url := options.Options.S3Endpoint
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
@@ -197,6 +209,7 @@ func initS3() {
}
url = prefix + url
}
err := s3.Init(
url,
options.Options.S3AccessKey,
@@ -207,6 +220,11 @@ func initS3() {
if err != nil {
log.Fatalf("failed init s3 client %s", err)
}
// clear glance bucket lifecycle definiton
if err = s3.SetBucketLifecycle(""); err != nil {
log.Warningf("remove onecloud-screendump lifecycle %s", err)
}
func() {
fd, err := os.OpenFile("/tmp/s3-pass", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {

View File

@@ -517,22 +517,45 @@ func (self *SRegion) CreateInstance(hostId, hypervisor string, opts *cloudprovid
if opts.BillingCycle != nil {
input.Duration = opts.BillingCycle.String()
}
input.Disks = append(input.Disks, &api.DiskConfig{
image, err := self.GetImage(opts.ExternalImageId)
if err != nil {
return nil, errors.Wrapf(err, "GetImage")
}
imageId := opts.ExternalImageId
if image.DiskFormat == "iso" {
input.Cdrom = opts.ExternalImageId
imageId = ""
}
sysDisk := &api.DiskConfig{
Index: 0,
ImageId: opts.ExternalImageId,
ImageId: imageId,
DiskType: api.DISK_TYPE_SYS,
SizeMb: opts.SysDisk.SizeGB * 1024,
Backend: opts.SysDisk.StorageType,
Storage: opts.SysDisk.StorageExternalId,
})
}
if len(opts.SysDisk.Driver) > 0 {
sysDisk.Driver = opts.SysDisk.Driver
}
if len(opts.SysDisk.CacheMode) > 0 {
sysDisk.Cache = opts.SysDisk.CacheMode
}
input.Disks = append(input.Disks, sysDisk)
for idx, disk := range opts.DataDisks {
input.Disks = append(input.Disks, &api.DiskConfig{
dataDisk := &api.DiskConfig{
Index: idx + 1,
DiskType: api.DISK_TYPE_DATA,
SizeMb: disk.SizeGB * 1024,
Backend: disk.StorageType,
Storage: disk.StorageExternalId,
})
}
if len(disk.Driver) > 0 {
dataDisk.Driver = disk.Driver
}
if len(disk.CacheMode) > 0 {
dataDisk.Cache = disk.CacheMode
}
input.Disks = append(input.Disks, dataDisk)
}
input.Networks = append(input.Networks, &api.NetworkConfig{
Index: 0,

View File

@@ -29,7 +29,7 @@ func init() {
"baremetalnetworks",
[]string{"Baremetal_ID", "Host",
"Network_ID", "Network", "IP_addr", "Mac_addr",
"Nic_Type"},
"Nic_Type", "Vlan_ID"},
[]string{},
&Hosts,
&Networks)

View File

@@ -78,30 +78,34 @@ func (o DiskCreateOptions) Params() (*api.DiskCreateInput, error) {
return params, nil
}
type DiskMigrateOptions struct {
ID string `help:"ID of the server" json:"-"`
TargetStorageId string `help:"Disk migrate target storage id or name" json:"target_storage_id"`
type DiskIdOptions struct {
ID string `help:"ID or Name of disk"`
}
func (o *DiskMigrateOptions) GetId() string {
func (o *DiskIdOptions) GetId() string {
return o.ID
}
func (o *DiskIdOptions) Params() (jsonutils.JSONObject, error) {
return nil, nil
}
type DiskMigrateOptions struct {
DiskIdOptions
TargetStorageId string `help:"Disk migrate target storage id or name" json:"target_storage_id"`
}
func (o *DiskMigrateOptions) Params() (jsonutils.JSONObject, error) {
return options.StructToParams(o)
}
type DiskChangeStorageTypeOptions struct {
ID string `help:"ID of the server" json:"-"`
DiskIdOptions
StorageType string `help:"Disk migrate target storage type" json:"storage_type"`
}
func (o *DiskChangeStorageTypeOptions) GetId() string {
return o.ID
}
func (o *DiskChangeStorageTypeOptions) Params() (jsonutils.JSONObject, error) {
return options.StructToParams(o)
}
@@ -124,7 +128,9 @@ type DiskListOptions struct {
SnapshotpolicyId string `help:"snapshotpolicy id"`
StorageHostId string `help:"filter disk by host"`
StorageHostId string `help:"filter disk by host"`
BindingServerSnapshotpolicy *bool `help:"filter disk by binding server snapshotpolicy" negative:"no-binding-server-snapshotpolicy"`
BindingSnapshotpolicy *bool `help:"filter disk by binding snapshotpolicy" negative:"no-binding-snapshotpolicy"`
}
func (opts *DiskListOptions) Params() (jsonutils.JSONObject, error) {
@@ -143,14 +149,10 @@ func (opts *DiskListOptions) Params() (jsonutils.JSONObject, error) {
}
type DiskChangeBillingTypeOptions struct {
ID string
DiskIdOptions
BillingType string `choices:"prepaid|postpaid"`
}
func (o *DiskChangeBillingTypeOptions) GetId() string {
return o.ID
}
func (o *DiskChangeBillingTypeOptions) Params() (jsonutils.JSONObject, error) {
return jsonutils.Marshal(map[string]string{"billing_type": o.BillingType}), nil
}

View File

@@ -79,8 +79,10 @@ type ServerListOptions struct {
WithUserMeta *bool `help:"filter by user metadata" negative:"without_user_meta"`
WithHost *bool `help:"filter guest with host or not" negative:"without_host"`
SnapshotpolicyId string `help:"filter guest with snapshotpolicy or not" json:"snapshotpolicy_id"`
WithHost *bool `help:"filter guest with host or not" negative:"without_host"`
SnapshotpolicyId string `help:"filter guest with snapshotpolicy or not" json:"snapshotpolicy_id"`
BindingDisksSnapshotpolicy *bool `help:"filter guest with disks binding snapshotpolicy or not" negative:"no-binding-disks-snapshotpolicy" json:"binding_disks_snapshotpolicy"`
BindingSnapshotpolicy *bool `help:"filter guest with binding snapshotpolicy or not" negative:"no-binding-snapshotpolicy" json:"binding_snapshotpolicy"`
}
func (o *ServerListOptions) Params() (jsonutils.JSONObject, error) {
@@ -1498,6 +1500,16 @@ func (o *ServerScreenDumpOptions) Params() (jsonutils.JSONObject, error) {
return jsonutils.Marshal(o), nil
}
type ServerSetNetworkNumQueues struct {
ServerIdOptions
MacAddr string `help:"server network mac addr"`
NumQueues int `help:"network num queues"`
}
func (o *ServerSetNetworkNumQueues) Params() (jsonutils.JSONObject, error) {
return jsonutils.Marshal(o), nil
}
type ServerIsoOptions struct {
ServerIdOptions
Ordinal int `help:"server iso ordinal, default 0"`

View File

@@ -61,6 +61,7 @@ type ServerSkusCreateOptions struct {
Name string `help:"ServerSku name"`
CpuCoreCount int `help:"Cpu Count" required:"true" positional:"true"`
MemorySizeMB int `help:"Memory MB" required:"true" positional:"true"`
CpuArch string `help:"CPU architecture" choices:"x86|aarch64"`
OsName *string `help:"OS name/type" choices:"Linux|Windows|Any" default:"Any"`
InstanceTypeCategory *string `help:"instance type category" choices:"general_purpose|compute_optimized|memory_optimized|storage_optimized|hardware_accelerated|high_memory|high_storage"`

View File

@@ -41,6 +41,7 @@ type RobotCreateOptions struct {
Header string
Body string
MsgKey string
SecretKey string
UseTemplate bool `help:"just for webhook"`
}
@@ -90,6 +91,7 @@ type SrobotUpdateOptions struct {
Header *string
Body *string
MsgKey string
SecretKey string
UseTemplate tristate.TriState
}

View File

@@ -262,13 +262,10 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred
message := jsonutils.Marshal(input.ResourceDetails).String()
// append default receiver
if len(input.Event) == 0 {
for _, receiver := range input.ReceiverIds {
// receiverIds = append(receiverIds, api.SReceiverWithGroupTimes{ReceiverId: receiver})
if _, ok := receiverIds[receiver]; !ok {
receiverIds[receiver] = 0
}
for _, receiver := range input.ReceiverIds {
// receiverIds = append(receiverIds, api.SReceiverWithGroupTimes{ReceiverId: receiver})
if _, ok := receiverIds[receiver]; !ok {
receiverIds[receiver] = 0
}
}
@@ -316,6 +313,7 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred
}
err := nm.create(ctx, userCred, ct, realReceiverIds, nil, input.Priority, event.GetId(), topic.GetId(), topic.Type)
if err != nil {
log.Errorf("unable to create notification for %s: %v", ct, err)
output.FailedList = append(output.FailedList, api.FailedElem{
ContactType: ct,
Reason: err.Error(),
@@ -324,6 +322,7 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred
}
err = nm.createWithWebhookRobots(ctx, userCred, webhookRobots, input.Priority, event.GetId(), topic.Type)
if err != nil {
log.Errorf("unable to create notification for webhook robots: %v", err)
output.FailedList = append(output.FailedList, api.FailedElem{
ContactType: api.WEBHOOK,
Reason: err.Error(),
@@ -332,6 +331,7 @@ func (nm *SNotificationManager) PerformEventNotify(ctx context.Context, userCred
// robot
err = nm.createWithRobots(ctx, userCred, realRobot, input.Priority, event.GetId(), topic.Type)
if err != nil {
log.Errorf("unable to create notification for robots: %v", err)
output.FailedList = append(output.FailedList, api.FailedElem{
ContactType: api.ROBOT,
Reason: err.Error(),
@@ -365,6 +365,7 @@ func (nm *SNotificationManager) PerformContactNotify(ctx context.Context, userCr
params.Header = robot.Header
params.Body = robot.Body
params.MsgKey = robot.MsgKey
params.SecretKey = robot.SecretKey
params.Receivers = api.SNotifyReceiver{
Contact: robot.Address,
}
@@ -487,11 +488,9 @@ func (nm *SNotificationManager) create(ctx context.Context, userCred mcclient.To
n.SetModelManager(nm, n)
task, err := taskman.TaskManager.NewTask(ctx, "NotificationSendTask", n, userCred, nil, "", "")
if err != nil {
log.Errorf("NotificationSendTask newTask error %v", err)
} else {
task.ScheduleRun(nil)
return errors.Wrapf(err, "NewTask")
}
return nil
return task.ScheduleRun(nil)
}
func (nm *SNotificationManager) createWithWebhookRobots(ctx context.Context, userCred mcclient.TokenCredential, webhookRobotIds []string, priority, eventId string, topicType string) error {

View File

@@ -60,13 +60,15 @@ type SRobot struct {
db.SSharableVirtualResourceBase
db.SEnabledResourceBase
Type string `width:"16" nullable:"false" create:"required" get:"user" list:"user" index:"true"`
Address string `nullable:"false" create:"required" update:"user" get:"user" list:"user"`
Lang string `width:"16" nullable:"false" create:"required" update:"user" get:"user" list:"user"`
Header jsonutils.JSONObject `length:"long" charset:"utf8" nullable:"true" list:"user" create:"optional" update:"user"`
Body jsonutils.JSONObject `length:"long" charset:"utf8" nullable:"true" list:"user" create:"optional" update:"user"`
MsgKey string `width:"16" nullable:"true" update:"user" get:"user" list:"user"`
UseTemplate tristate.TriState `default:"false" list:"domain" update:"user" create:"admin_optional"`
Type string `width:"16" nullable:"false" create:"required" get:"user" list:"user" index:"true"`
Address string `nullable:"false" create:"required" update:"user" get:"user" list:"user"`
Lang string `width:"16" nullable:"false" create:"required" update:"user" get:"user" list:"user"`
Header jsonutils.JSONObject `length:"long" charset:"utf8" nullable:"true" list:"user" create:"optional" update:"user"`
Body jsonutils.JSONObject `length:"long" charset:"utf8" nullable:"true" list:"user" create:"optional" update:"user"`
MsgKey string `width:"16" nullable:"true" update:"user" get:"user" list:"user"`
// webhook 签名加密
SecretKey string `width:"128" nullable:"true" update:"user"`
UseTemplate tristate.TriState `default:"false" list:"domain" update:"user" create:"admin_optional"`
}
var RobotList = []string{api.FEISHU_ROBOT, api.DINGTALK_ROBOT, api.WORKWX_ROBOT, api.WEBHOOK, api.WEBHOOK_ROBOT}
@@ -98,11 +100,12 @@ func (rm *SRobotManager) ValidateCreateData(ctx context.Context, userCred mcclie
Contact: input.Address,
DomainId: input.ProjectDomainId,
},
Header: input.Header,
Body: input.Body,
MsgKey: input.MsgKey,
Title: "Validate",
Message: "This is a verification message, please ignore.",
Header: input.Header,
Body: input.Body,
MsgKey: input.MsgKey,
SecretKey: input.SecretKey,
Title: "Validate",
Message: "This is a verification message, please ignore.",
})
if err != nil {
if errors.ErrConnectRefused == errors.Cause(err) {
@@ -158,11 +161,12 @@ func (r *SRobot) ValidateUpdateData(ctx context.Context, userCred mcclient.Token
// check Address
dirver := GetDriver(fmt.Sprintf("%s-robot", r.Type))
err := dirver.Send(ctx, api.SendParams{
Header: input.Header,
Body: input.Body,
MsgKey: input.MsgKey,
Title: "Validate",
Message: "This is a verification message, please ignore.",
Header: input.Header,
Body: input.Body,
MsgKey: input.MsgKey,
SecretKey: input.SecretKey,
Title: "Validate",
Message: "This is a verification message, please ignore.",
Receivers: api.SNotifyReceiver{
Contact: input.Address,
},

View File

@@ -129,6 +129,7 @@ func (emailSender *SEmailSender) Send(ctx context.Context, args api.SendParams)
errs := make([]error, 0)
for tryTime := 3; tryTime > 0; tryTime-- {
err = gomail.Send(sender, gmsg)
log.Debugf("send %s to %s email err: %v", args.EmailMsg.Subject, to, err)
if err != nil {
errs = append(errs, errors.Wrapf(err, "Send"))
time.Sleep(time.Second * 10)

View File

@@ -15,6 +15,9 @@ package sender
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"net/http"
"strings"
@@ -35,6 +38,15 @@ func (self *SWebhookSender) GetSenderType() string {
return api.WEBHOOK_ROBOT
}
func GenerateHMACSignature(payload []byte, secret string) string {
// 创建HMAC哈希器
h := hmac.New(sha256.New, []byte(secret))
// 写入要签名的数据
h.Write(payload)
// 计算哈希值并转为16进制字符串
return hex.EncodeToString(h.Sum(nil))
}
func (self *SWebhookSender) Send(ctx context.Context, args api.SendParams) error {
dict := jsonutils.NewDict()
header := http.Header{}
@@ -75,6 +87,11 @@ func (self *SWebhookSender) Send(ctx context.Context, args api.SendParams) error
}
}
if len(args.SecretKey) > 0 {
signature := GenerateHMACSignature([]byte(jsonutils.Marshal(dict).String()), args.SecretKey)
header.Set(api.WEBHOOK_SIGNATURE_HEADER, signature)
}
_, _, err := httputils.JSONRequest(cli, ctx, httputils.POST, args.Receivers.Contact, header, dict, false)
return errors.Wrap(err, "webhook send")
}

View File

@@ -140,7 +140,8 @@ func (self *NotificationSendTask) OnInit(ctx context.Context, obj db.IStandalone
continue
}
if !verified {
sendFail(&rns[i], fmt.Sprintf("unverified contactType %q", notification.ContactType))
contact, _ := receiver.GetContact(notification.ContactType)
sendFail(&rns[i], fmt.Sprintf("unverified contactType %q for contact %s", notification.ContactType, contact))
continue
}
lang, err := receiver.GetTemplateLang(ctx)
@@ -179,7 +180,7 @@ func (self *NotificationSendTask) OnInit(ctx context.Context, obj db.IStandalone
apis.TEMPLATE_LANG_EN: receiversEn,
} {
if len(receivers) == 0 {
log.Warningf("no receiver to send, skip ...")
log.Warningf("no receiver to send for %s %s, skip ...", notification.ContactType, lang)
continue
}
// send
@@ -265,6 +266,7 @@ func (notificationSendTask *NotificationSendTask) batchSend(ctx context.Context,
params.Header = robot.Header
params.Body = robot.Body
params.MsgKey = robot.MsgKey
params.SecretKey = robot.SecretKey
params.GroupTimes = uint(receivers[i].rNotificaion.GroupTimes)
err = driver.Send(ctx, params)
if err != nil {

View File

@@ -0,0 +1,132 @@
// 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"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/sqlchemy"
api "yunion.io/x/onecloud/pkg/apis/yunionconf"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/stringutils2"
)
// +onecloud:swagger-gen-model-singular=tag
// +onecloud:swagger-gen-model-plural=tags
type STagManager struct {
db.SInfrasResourceBaseManager
}
var (
TagManager *STagManager
)
func init() {
TagManager = &STagManager{
SInfrasResourceBaseManager: db.NewInfrasResourceBaseManager(
STag{},
"tags_tbl",
"tag",
"tags",
),
}
TagManager.SetVirtualObject(TagManager)
}
type STag struct {
db.SInfrasResourceBase
Values []string `charset:"utf8" get:"user" list:"user" update:"admin" create:"required"`
}
// 预置标签列表
func (manager *STagManager) ListItemFilter(
ctx context.Context,
q *sqlchemy.SQuery,
userCred mcclient.TokenCredential,
query api.TagListInput,
) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SInfrasResourceBaseManager.ListItemFilter(ctx, q, userCred, query.InfrasResourceBaseListInput)
if err != nil {
return nil, errors.Wrap(err, "SInfrasResourceBaseManager.ListItemFilter")
}
return q, nil
}
func (manager *STagManager) OrderByExtraFields(
ctx context.Context,
q *sqlchemy.SQuery,
userCred mcclient.TokenCredential,
query api.TagListInput,
) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SInfrasResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.InfrasResourceBaseListInput)
if err != nil {
return nil, errors.Wrap(err, "SInfrasResourceBaseManager.OrderByExtraFields")
}
return q, nil
}
func (manager *STagManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) {
var err error
q, err = manager.SInfrasResourceBaseManager.QueryDistinctExtraField(q, field)
if err == nil {
return q, nil
}
return q, httperrors.ErrNotFound
}
func (manager *STagManager) FetchCustomizeColumns(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
objs []interface{},
fields stringutils2.SSortedStrings,
isList bool,
) []api.TagDetails {
rows := make([]api.TagDetails, len(objs))
stdRows := manager.SInfrasResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList)
for i := range rows {
rows[i] = api.TagDetails{
InfrasResourceBaseDetails: stdRows[i],
}
}
return rows
}
func (manager *STagManager) ValidateCreateData(
ctx context.Context,
userCred mcclient.TokenCredential,
ownerId mcclient.IIdentityProvider,
query jsonutils.JSONObject,
input *api.TagCreateInput,
) (*api.TagCreateInput, error) {
var err error
input.InfrasResourceBaseCreateInput, err = manager.SInfrasResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.InfrasResourceBaseCreateInput)
if err != nil {
return input, errors.Wrap(err, "SInfrasResourceBaseManager.ValidateCreateData")
}
return input, nil
}

View File

@@ -24,3 +24,14 @@ type YunionConfOptions struct {
var (
Options YunionConfOptions
)
func OnOptionsChange(oldO, newO interface{}) bool {
oldOpts := oldO.(*YunionConfOptions)
newOpts := newO.(*YunionConfOptions)
changed := false
if common_options.OnCommonOptionsChange(&oldOpts.CommonOptions, &newOpts.CommonOptions) {
changed = true
}
return changed
}

View File

@@ -93,6 +93,24 @@ var (
},
},
},
{
Auth: true,
Scope: rbacscope.ScopeProject,
Rules: []rbacutils.SRbacRule{
{
Service: api.SERVICE_TYPE,
Resource: "tags",
Action: PolicyActionList,
Result: rbacutils.Allow,
},
{
Service: api.SERVICE_TYPE,
Resource: "tags",
Action: PolicyActionGet,
Result: rbacutils.Allow,
},
},
},
}
)

View File

@@ -52,6 +52,7 @@ func InitHandlers(app *appsrv.Application) {
models.ParameterManager,
models.ScopedPolicyBindingManager,
models.ScopedPolicyManager,
models.TagManager,
} {
db.RegisterModelManager(manager)
handler := db.NewModelHandler(manager)

View File

@@ -46,6 +46,7 @@ func StartService() {
app_common.InitAuth(commonOpts, func() {
log.Infof("Auth complete!!")
})
common_options.StartOptionManager(opts, opts.ConfigSyncPeriodSeconds, api.SERVICE_TYPE, api.SERVICE_VERSION, options.OnOptionsChange)
cloudcommon.InitDB(dbOpts)

2
vendor/modules.txt vendored
View File

@@ -1589,7 +1589,7 @@ sigs.k8s.io/structured-merge-diff/v4/value
# sigs.k8s.io/yaml v1.2.0
## explicit; go 1.12
sigs.k8s.io/yaml
# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260123023413-f5d35910430c
# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260207043119-2907d68518c5
## explicit; go 1.21
yunion.io/x/cloudmux/pkg/apis
yunion.io/x/cloudmux/pkg/apis/billing

View File

@@ -102,6 +102,8 @@ type SDiskInfo struct {
StorageType string
SizeGB int
Iops int
Driver string
CacheMode string
Name string
// aws gp3 only
Throughput int

View File

@@ -192,13 +192,16 @@ func (r *SRegion) ListAccounts() ([]SAccount, error) {
for _, actPtr := range parts.Accounts {
account := SAccount{
ID: *actPtr.Id,
Name: *actPtr.Name,
Name: *actPtr.Id,
Arn: *actPtr.Arn,
Email: *actPtr.Email,
Status: *actPtr.Status,
JoinedMethod: *actPtr.JoinedMethod,
JoinedTimestamp: *actPtr.JoinedTimestamp,
}
if actPtr.Name != nil && len(*actPtr.Name) > 0 {
account.Name = *actPtr.Name
}
if *orgOutput.Organization.MasterAccountId == *actPtr.Id {
account.IsMaster = true
}