mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/yunionio/cloudpods.git
synced 2026-09-20 08:03:53 +08:00
Compare commits
28 Commits
release/4.
...
v4.0.4-rc.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
203f5bb396 | ||
|
|
4a729a7639 | ||
|
|
712ce702ce | ||
|
|
80ea7e60e1 | ||
|
|
daf21aa6a3 | ||
|
|
6bc05ca5c1 | ||
|
|
ea0e1ca42f | ||
|
|
94862f5232 | ||
|
|
887bb943b8 | ||
|
|
205dbe2f64 | ||
|
|
554cdd01be | ||
|
|
da03a421a5 | ||
|
|
06517e5aa6 | ||
|
|
6d49bee5e6 | ||
|
|
033408e7d0 | ||
|
|
fe1846f690 | ||
|
|
7aa20811f5 | ||
|
|
93971e83fb | ||
|
|
1950824dfa | ||
|
|
7fec30216b | ||
|
|
23e1910817 | ||
|
|
a8f3474fb2 | ||
|
|
bee68803b2 | ||
|
|
f58fcc2999 | ||
|
|
fcbf833fde | ||
|
|
af2e410c24 | ||
|
|
f6e32e7707 | ||
|
|
277546c95c |
@@ -1,4 +1,4 @@
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/baremetal-base:v0.3.9-20251215.0
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/baremetal-base:v0.3.9-20260724.0
|
||||
|
||||
LABEL maintainer="Zexi Li <lizexi@yunionyun.com>"
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#RUN yum install -y https://iso.yunion.cn/vm-images/baremetal-pxerom-1.1.0-21092209.x86_64.rpm
|
||||
#RUN yum install -y http://192.168.23.50:8083/baremetal-pxerom-1.1.0-21092209.x86_64.rpm
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/yunionos:v4.0.0-20251201.0 as yunionos
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/yunionos:v4.0.0-20251203.0 as yunionos
|
||||
|
||||
FROM centos:8 as grub-stage
|
||||
FROM registry.cn-beijing.aliyuncs.com/cloudpods/centos:8 as grub-stage
|
||||
|
||||
RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
|
||||
RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
REGISTRY ?= "registry.cn-beijing.aliyuncs.com/yunionio"
|
||||
DOCKER_BUILD = docker build -t $(REGISTRY)
|
||||
DOCKER_BUILDX = docker buildx build --platform linux/arm64,linux/amd64,linux/riscv64 --push -t $(REGISTRY)
|
||||
DOCKER_BUILDX_BM = docker buildx build --platform linux/arm64,linux/amd64 --push -t $(REGISTRY)
|
||||
|
||||
|
||||
debian10-base:
|
||||
@@ -40,7 +41,7 @@ WEBCONSOLE_BASE_VERSION_3-22-2 = 3.22.2-1
|
||||
webconsole-base:
|
||||
$(DOCKER_BUILDX)/webconsole-base:$(WEBCONSOLE_BASE_VERSION_3-22-2) -f ./Dockerfile.webconsole-base .
|
||||
|
||||
BAREMETAL_BASE_VERSION = v0.3.9-20251112.1
|
||||
BAREMETAL_BASE_VERSION = v0.3.9-20260724.0
|
||||
|
||||
FEDORA_RISCV64_VERSION = 42
|
||||
fedora-riscv64-base:
|
||||
@@ -53,8 +54,9 @@ baremetal-base-riscv: fedora-riscv64-base
|
||||
$(DOCKER_BUILDX)/baremetal-base:$(BAREMETAL_BASE_VERSION) -f ./Dockerfile.baremetal-base-riscv .
|
||||
#docker push $(REGISTRY)/baremetal-base:$(BAREMETAL_BASE_VERSION)
|
||||
|
||||
# TODO: support riscv64 for baremetal-base
|
||||
baremetal-base:
|
||||
$(DOCKER_BUILDX)/baremetal-base:$(BAREMETAL_BASE_VERSION) -f ./Dockerfile.baremetal-base .
|
||||
$(DOCKER_BUILDX_BM)/baremetal-base:$(BAREMETAL_BASE_VERSION) -f ./Dockerfile.baremetal-base .
|
||||
#docker push $(REGISTRY)/baremetal-base:$(BAREMETAL_BASE_VERSION)
|
||||
|
||||
TORRENT_VERSION = 20210815.0
|
||||
|
||||
@@ -58,7 +58,7 @@ func init() {
|
||||
|
||||
type HostNetworkDetailOptions struct {
|
||||
HOST string `help:"ID or Name of Host"`
|
||||
NETWORK string `help:"ID or Name of Wire"`
|
||||
NETWORK string `help:"ID or Name of Network"`
|
||||
}
|
||||
R(&HostNetworkDetailOptions{}, "host-network-show", "Show baremetal network details", func(s *mcclient.ClientSession, args *HostNetworkDetailOptions) error {
|
||||
result, err := modules.Baremetalnetworks.Get(s, args.HOST, args.NETWORK, nil)
|
||||
|
||||
@@ -24,7 +24,7 @@ func init() {
|
||||
|
||||
cmd := shell.NewResourceCmd(&modules.Cloudaccounts).WithKeyword("cloud-account")
|
||||
cmd.List(&options.CloudaccountListOptions{})
|
||||
cmd.Show(&options.SCloudAccountIdOptions{})
|
||||
cmd.Show(&options.CloudaccountShowOptions{})
|
||||
cmd.Delete(&options.SCloudAccountIdOptions{})
|
||||
cmd.Update(&options.SCloudAccountUpdateBaseOptions{})
|
||||
// cmd.PerformClassWithKeyword("preparenets-vmware", "prepare-nets", &options.SVMwareCloudAccountPrepareNetsOptions{})
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
func init() {
|
||||
type CloudproviderRegionListOptions struct {
|
||||
options.BaseListOptions
|
||||
Region string `help:"ID or Name of Host"`
|
||||
Region string `help:"ID or Name of Region"`
|
||||
|
||||
Capability []string `help:"capability filter" choices:"project|compute|network|loadbalancer|objectstore|rds|cache|event"`
|
||||
}
|
||||
|
||||
@@ -34,16 +34,18 @@ func init() {
|
||||
cmd.Perform("purge", &compute.CloudregionPurgeOptions{})
|
||||
|
||||
type CloudregionListOptions struct {
|
||||
_ struct{} `mcp-desc:"【创建流程中的中间步骤】本工具不能完成创建。指定云厂商时必须传 provider,例如创建阿里云:provider=[\"Aliyun\"]。创建虚拟机时必须 usable=true(MCP 默认注入)。查完后继续 capability/镜像/sku,最后 climc_server_create。严禁只查区域后停止"`
|
||||
|
||||
options.BaseListOptions
|
||||
|
||||
Usable *bool `help:"List regions where networks are usable"`
|
||||
UsableVpc *bool `help:"List regions where VPC are usable"`
|
||||
Service string `help:"List regions which service has available skus" choices:"dbinstances|servers|elasticcaches"`
|
||||
Usable *bool `help:"只列网络可用的区域;创建虚拟机时必须为 true(MCP 默认注入 usable=true)" mcp:"true"`
|
||||
UsableVpc *bool `help:"List regions where VPC are usable" mcp:"true"`
|
||||
Service string `help:"List regions which service has available skus" choices:"dbinstances|servers|elasticcaches" mcp:"true"`
|
||||
ReadOnly *bool `help:"List regions with read only account"`
|
||||
|
||||
City string `help:"List regions in the specified city"`
|
||||
City string `help:"List regions in the specified city" mcp:"true"`
|
||||
|
||||
Capability []string `help:"capability filter" choices:"project|compute|network|loadbalancer|objectstore|rds|cache|event"`
|
||||
Capability []string `help:"capability filter" choices:"project|compute|network|loadbalancer|objectstore|rds|cache|event" mcp:"true"`
|
||||
|
||||
DistinctField string `help:"list the specified distinct field, e.g. city, region"`
|
||||
|
||||
@@ -149,7 +151,7 @@ func init() {
|
||||
Desc string `help:"Description" json:"description" token:"desc"`
|
||||
Latitude float32 `help:"region geographical location - latitude"`
|
||||
Longitude float32 `help:"region geographical location - longitude"`
|
||||
City string `help:"region geograpical location - city, e.g. Beijing, Frankfurt"`
|
||||
City string `help:"region geographical location - city, e.g. Beijing, Frankfurt"`
|
||||
CountryCode string `help:"region geographical location - ISO country code, e.g. CN"`
|
||||
}
|
||||
R(&CloudregionCreateOptions{}, "cloud-region-create", "Create a cloud region", func(s *mcclient.ClientSession, args *CloudregionCreateOptions) error {
|
||||
@@ -192,7 +194,7 @@ func init() {
|
||||
Desc string `help:"Description of the region" json:"description" token:"desc"`
|
||||
Latitude float32 `help:"region geographical location - latitude"`
|
||||
Longitude float32 `help:"region geographical location - longitude"`
|
||||
City string `help:"region geograpical location - city, e.g. Beijing, Frankfurt"`
|
||||
City string `help:"region geographical location - city, e.g. Beijing, Frankfurt"`
|
||||
CountryCode string `help:"region geographical location - ISO country code, e.g. CN"`
|
||||
}
|
||||
R(&CloudregionUpdateOptions{}, "cloud-region-update", "Update a cloud region", func(s *mcclient.ClientSession, args *CloudregionUpdateOptions) error {
|
||||
@@ -224,8 +226,10 @@ func init() {
|
||||
})
|
||||
|
||||
type CloudregionCapabiltyOptions struct {
|
||||
ID string `help:"ID or name of cloud region to check" json:"-"`
|
||||
Domain string `help:"cloud region domain"`
|
||||
_ struct{} `mcp-desc:"【创建流程中的中间步骤】查询区域能力,尤其是可用磁盘存储类型(storage_types2 / system_storage_types)。公有云创建前必须调用:ID 用 climc_cloud_region_list 返回的 id。从返回中选系统盘 backend(如 cloud_essd、cloud_ssd),写入 disk 的 backend=。查完继续镜像/网络/sku,最后 climc_server_create"`
|
||||
|
||||
ID string `help:"ID or name of cloud region to check;必须用 cloud-region-list 的 id" json:"-" mcp:"required"`
|
||||
Domain string `help:"cloud region domain" mcp:"true"`
|
||||
|
||||
ShowEmulated bool `help:"show emulated cloud region"`
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ var (
|
||||
R = shell.R
|
||||
printList = shell.PrintList
|
||||
printObject = shell.PrintObject
|
||||
printBatchResults = printutils.PrintJSONBatchResults
|
||||
printBatchResults = shell.PrintBatchResults
|
||||
|
||||
InvalidUpdateError = shell.InvalidUpdateError
|
||||
printObjectRecursive = printutils.PrintJSONObjectRecursive
|
||||
|
||||
@@ -29,7 +29,7 @@ func init() {
|
||||
cmd.List(&compute.DBInstanceListOptions{})
|
||||
cmd.Create(&compute.DBInstanceCreateOptions{})
|
||||
cmd.Update(&compute.DBInstanceUpdateOptions{})
|
||||
cmd.Show(&compute.DBInstanceIdOptions{})
|
||||
cmd.Show(&compute.DBInstanceShowOptions{})
|
||||
cmd.Delete(&compute.DBInstanceDeleteOptions{})
|
||||
cmd.Perform("renew", &compute.DBInstanceRenewOptions{})
|
||||
cmd.Perform("change-config", &compute.DBInstanceChangeConfigOptions{})
|
||||
|
||||
@@ -42,7 +42,7 @@ import (
|
||||
func init() {
|
||||
cmd := shell.NewResourceCmd(&modules.Disks)
|
||||
cmd.List(&compute_options.DiskListOptions{})
|
||||
cmd.Show(&compute_options.DiskIdOptions{})
|
||||
cmd.Show(&compute_options.DiskShowOptions{})
|
||||
cmd.Perform("public", &compute_options.DiskIdOptions{})
|
||||
cmd.Perform("private", &compute_options.DiskIdOptions{})
|
||||
cmd.Perform("syncstatus", &compute_options.DiskIdOptions{})
|
||||
@@ -55,13 +55,7 @@ func init() {
|
||||
cmd.Perform("change-billing-type", new(compute_options.DiskChangeBillingTypeOptions))
|
||||
cmd.Perform("change-storage-type", &compute_options.DiskChangeStorageTypeOptions{})
|
||||
|
||||
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"`
|
||||
DeleteSnapshots bool `help:"Delete disk snapshots before delete disk"`
|
||||
}
|
||||
|
||||
R(&DiskDeleteOptions{}, "disk-delete", "Delete a disk", func(s *mcclient.ClientSession, args *DiskDeleteOptions) error {
|
||||
R(&compute_options.DiskDeleteOptions{}, "disk-delete", "Delete a disk", func(s *mcclient.ClientSession, args *compute_options.DiskDeleteOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
if args.OverridePendingDelete {
|
||||
params.Add(jsonutils.JSONTrue, "override_pending_delete")
|
||||
@@ -100,7 +94,7 @@ func init() {
|
||||
AutoSnapshot string `help:"enable/disable auto snapshot of disk" choices:"enable|disable"`
|
||||
DiskType string `help:"Disk type" choices:"data|volume|sys"`
|
||||
IsSsd *bool `help:"mark disk as ssd" negative:"no-is-ssd"`
|
||||
AutoReset *bool `help:"Enable auto reset disk after geust shutdown"`
|
||||
AutoReset *bool `help:"Enable auto reset disk after guest shutdown"`
|
||||
}
|
||||
R(&DiskUpdateOptions{}, "disk-update", "Update property of a virtual disk", func(s *mcclient.ClientSession, args *DiskUpdateOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
@@ -173,11 +167,7 @@ func init() {
|
||||
return nil
|
||||
})
|
||||
|
||||
type DiskResizeOptions struct {
|
||||
DISK string `help:"ID or name of disk"`
|
||||
SIZE string `help:"Size of disk"`
|
||||
}
|
||||
R(&DiskResizeOptions{}, "disk-resize", "Resize a disk", func(s *mcclient.ClientSession, args *DiskResizeOptions) error {
|
||||
R(&compute_options.DiskResizeOptions{}, "disk-resize", "Resize a disk", func(s *mcclient.ClientSession, args *compute_options.DiskResizeOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
params.Add(jsonutils.NewString(args.SIZE), "size")
|
||||
disk, err := modules.Disks.PerformAction(s, args.DISK, "resize", params)
|
||||
@@ -189,7 +179,7 @@ func init() {
|
||||
})
|
||||
type DiskResetOptions struct {
|
||||
DISK string `help:"ID or name of disk"`
|
||||
SNAPSHOT string `help:"snapshots ID of disk"`
|
||||
SNAPSHOT string `help:"Snapshot ID of disk"`
|
||||
AutoStart bool `help:"Autostart guest"`
|
||||
}
|
||||
R(&DiskResetOptions{}, "disk-reset", "Resize a disk", func(s *mcclient.ClientSession, args *DiskResetOptions) error {
|
||||
@@ -245,9 +235,9 @@ func init() {
|
||||
|
||||
type DiskChangeOwnerOptions struct {
|
||||
ID string `help:"Disk to change owner" json:"-"`
|
||||
PROJECT string `help:"Project ID or change" json:"tenant"`
|
||||
PROJECT string `help:"Project ID or name" json:"tenant"`
|
||||
}
|
||||
R(&DiskChangeOwnerOptions{}, "disk-change-owner", "Change owner porject of a disk", func(s *mcclient.ClientSession, opts *DiskChangeOwnerOptions) error {
|
||||
R(&DiskChangeOwnerOptions{}, "disk-change-owner", "Change owner project of a disk", func(s *mcclient.ClientSession, opts *DiskChangeOwnerOptions) error {
|
||||
params, err := options.StructToParams(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
func init() {
|
||||
cmd := shell.NewResourceCmd(&modules.ElasticCache).WithKeyword("elastic-cache")
|
||||
cmd.List(&compute.ElasticCacheListOptions{})
|
||||
cmd.Show(&compute.ElasticCacheIdOption{})
|
||||
cmd.Show(&compute.ElasticCacheShowOptions{})
|
||||
cmd.Create(&compute.ElasticCacheCreateOptions{})
|
||||
cmd.Delete(&compute.ElasticCacheIdOption{})
|
||||
cmd.Perform("restart", &compute.ElasticCacheIdOption{})
|
||||
|
||||
@@ -27,7 +27,7 @@ func init() {
|
||||
cmd.Create(&compute.EipCreateOptions{})
|
||||
cmd.Delete(&options.BaseIdOptions{})
|
||||
cmd.Update(&compute.EipUpdateOptions{})
|
||||
cmd.Show(&options.BaseShowOptions{})
|
||||
cmd.Show(&compute.EipShowOptions{})
|
||||
cmd.Perform("purge", &options.BaseIdOptions{})
|
||||
cmd.Perform("associate", &compute.EipAssociateOptions{})
|
||||
cmd.Perform("dissociate", &compute.EipDissociateOptions{})
|
||||
|
||||
@@ -36,7 +36,7 @@ import (
|
||||
|
||||
func init() {
|
||||
cmd := shell.NewResourceCmd(&modules.Hosts)
|
||||
cmd.List(&compute.HostListOptions{})
|
||||
cmd.List(&compute.HostListForMcpOptions{})
|
||||
cmd.GetMetadata(&options.BaseIdOptions{})
|
||||
cmd.GetProperty(&compute.HostStatusStatisticsOptions{})
|
||||
cmd.Update(&compute.HostUpdateOptions{})
|
||||
|
||||
29
cmd/climc/shell/compute/ipsets.go
Normal file
29
cmd/climc/shell/compute/ipsets.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// 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 compute
|
||||
|
||||
import (
|
||||
"yunion.io/x/onecloud/cmd/climc/shell"
|
||||
modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/options/compute"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmd := shell.NewResourceCmd(&modules.IpSets)
|
||||
cmd.List(&compute.IpSetListOptions{})
|
||||
cmd.Create(&compute.IpSetCreateOptions{})
|
||||
cmd.Update(&compute.IpSetUpdateOptions{})
|
||||
cmd.Delete(&compute.IpSetIdOptions{})
|
||||
}
|
||||
@@ -26,8 +26,12 @@ func init() {
|
||||
cmd.List(&compute.DeviceListOptions{})
|
||||
cmd.Update(&compute.DeviceUpdateOptions{})
|
||||
cmd.Show(&compute.DeviceShowOptions{})
|
||||
cmd.Create(&compute.DeviceCreateOptions{})
|
||||
cmd.BatchDelete(&compute.DeviceDeleteOptions{})
|
||||
cmd.BatchPerform("purge", &compute.DevicePurgeOptions{})
|
||||
cmd.Perform("public", &options.SharableResourcePublicOptions{})
|
||||
cmd.Perform("private", &options.BaseIdOptions{})
|
||||
|
||||
scmd := shell.NewResourceCmd(&modules.ServerIsolatedDevices)
|
||||
scmd.List(&compute.ServerDeviceListOptions{})
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func init() {
|
||||
cmd := shell.NewResourceCmd(&modules.Kafkas)
|
||||
cmd.List(&compute.KafkaListOptions{})
|
||||
cmd.Update(&compute.KafkaUpdateOptions{})
|
||||
cmd.Show(&compute.KafkaIdOption{})
|
||||
cmd.Show(&compute.KafkaShowOptions{})
|
||||
cmd.Get("topics", &options.BaseIdOptions{})
|
||||
cmd.Delete(&compute.KafkaIdOption{})
|
||||
cmd.Perform("syncstatus", &compute.KafkaIdOption{})
|
||||
|
||||
@@ -25,7 +25,7 @@ func init() {
|
||||
cmd := shell.NewResourceCmd(&modules.MongoDB)
|
||||
cmd.List(&compute.MongoDBListOptions{})
|
||||
cmd.Update(&compute.MongoDBUpdateOptions{})
|
||||
cmd.Show(&options.BaseIdOptions{})
|
||||
cmd.Show(&compute.MongoDBShowOptions{})
|
||||
cmd.Delete(&options.BaseIdOptions{})
|
||||
cmd.Get("backups", &options.BaseIdOptions{})
|
||||
cmd.Perform("syncstatus", &options.BaseIdOptions{})
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
func init() {
|
||||
|
||||
type NetworkinterfaceNetworkListOptions struct {
|
||||
Networkinterface string `help:"ID or Name of Server"`
|
||||
Networkinterface string `help:"ID or Name of Networkinterface"`
|
||||
Network string `help:"Network ID or name"`
|
||||
}
|
||||
R(&NetworkinterfaceNetworkListOptions{}, "networkinterface-network-list", "List server network pairs", func(s *mcclient.ClientSession, args *NetworkinterfaceNetworkListOptions) error {
|
||||
|
||||
@@ -212,7 +212,7 @@ func init() {
|
||||
|
||||
type NetworkChangeOwnerOptions struct {
|
||||
ID string `help:"Network to change owner" json:"-"`
|
||||
PROJECT string `help:"Project ID or change" json:"tenant"`
|
||||
PROJECT string `help:"Project ID or name" json:"tenant"`
|
||||
}
|
||||
R(&NetworkChangeOwnerOptions{}, "network-change-owner", "Change owner project of a network", func(s *mcclient.ClientSession, args *NetworkChangeOwnerOptions) error {
|
||||
params, err := options.StructToParams(args)
|
||||
|
||||
@@ -24,10 +24,10 @@ import (
|
||||
|
||||
func init() {
|
||||
type NetworkReserveIPOptions struct {
|
||||
NETWORK string `help:"IP or name of network"`
|
||||
NETWORK string `help:"ID or name of network"`
|
||||
NOTES string `help:"Why reserve this IP"`
|
||||
IPS []string `help:"IPs to reserve"`
|
||||
Duration string `help:"reservation duration, e.g. 1I, 1H, 2M"`
|
||||
Duration string `help:"reservation duration, e.g. 1D, 1H, 2M"`
|
||||
Status string `help:"ip status"`
|
||||
}
|
||||
R(&NetworkReserveIPOptions{}, "network-reserve-ip", "Reserve an IP address from pool", func(s *mcclient.ClientSession, args *NetworkReserveIPOptions) error {
|
||||
@@ -49,7 +49,7 @@ func init() {
|
||||
})
|
||||
|
||||
type NetworkReleaseReservedIPOptions struct {
|
||||
NETWORK string `help:"IP or name of network"`
|
||||
NETWORK string `help:"ID or name of network"`
|
||||
IP string `help:"IP to release"`
|
||||
}
|
||||
R(&NetworkReleaseReservedIPOptions{}, "network-release-reserved-ip", "Release a reserved IP into pool", func(s *mcclient.ClientSession, args *NetworkReleaseReservedIPOptions) error {
|
||||
|
||||
@@ -17,15 +17,14 @@ package compute
|
||||
import (
|
||||
"yunion.io/x/onecloud/cmd/climc/shell"
|
||||
modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/options"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/options/compute"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cmd := shell.NewResourceCmd(&modules.SecGroupRules).WithKeyword("secgroup-rule")
|
||||
cmd.List(&compute.SecGroupRulesListOptions{})
|
||||
cmd.Show(&options.BaseShowOptions{})
|
||||
cmd.Delete(&options.BaseIdOptions{})
|
||||
cmd.Show(&compute.SecGroupRuleShowOptions{})
|
||||
cmd.Delete(&compute.SecGroupRuleDeleteOptions{})
|
||||
cmd.Create(&compute.SecGroupRulesCreateOptions{})
|
||||
cmd.Update(&compute.SecGroupRulesUpdateOptions{})
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@ func init() {
|
||||
cmd := shell.NewResourceCmd(&modules.SecGroups)
|
||||
cmd.List(&options.SecgroupListOptions{})
|
||||
cmd.Create(&options.SecgroupCreateOptions{})
|
||||
cmd.Show(&options.SecgroupIdOptions{})
|
||||
cmd.Show(&options.SecgroupShowOptions{})
|
||||
cmd.Update(&baseoptions.BaseUpdateOptions{})
|
||||
cmd.Delete(&options.SecgroupIdOptions{})
|
||||
cmd.Delete(&options.SecgroupDeleteOptions{})
|
||||
cmd.Perform("public", &options.SecgroupIdOptions{})
|
||||
cmd.Perform("syncstatus", &options.SecgroupIdOptions{})
|
||||
cmd.Perform("private", &options.SecgroupIdOptions{})
|
||||
|
||||
@@ -77,18 +77,10 @@ func init() {
|
||||
type ServerDiskUpdateOptions struct {
|
||||
SERVER string `help:"ID or Name of server"`
|
||||
DISK string `help:"ID or Name of Disk"`
|
||||
Cache string `help:"Cache mode of vDisk" choices:"writethrough|none|writeback|directsync"`
|
||||
Aio string `help:"Asynchronous IO mode of vDisk" choices:"native|threads"`
|
||||
Index int64 `help:"Index of vDisk" default:"-1"`
|
||||
}
|
||||
R(&ServerDiskUpdateOptions{}, "server-disk-update", "Update details of a virtual disk of a virtual server", func(s *mcclient.ClientSession, args *ServerDiskUpdateOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
if len(args.Cache) > 0 {
|
||||
params.Add(jsonutils.NewString(args.Cache), "cache_mode")
|
||||
}
|
||||
if len(args.Aio) > 0 {
|
||||
params.Add(jsonutils.NewString(args.Aio), "aio_mode")
|
||||
}
|
||||
if args.Index >= 0 {
|
||||
params.Add(jsonutils.NewInt(args.Index), "index")
|
||||
}
|
||||
@@ -121,10 +113,12 @@ func init() {
|
||||
})
|
||||
|
||||
type ServerAttachDiskOptions struct {
|
||||
_ struct{} `mcp-desc:"将已有硬盘挂载到虚机。SERVER/DISK 为 id/name;可选 driver。先 climc_server_list + climc_disk_list"`
|
||||
|
||||
SERVER string `help:"ID or name of server"`
|
||||
DISK string `help:"ID of name of disk to attach"`
|
||||
Driver string `help:"Driver" choices:"virtio|ide|scsi"`
|
||||
Cache string `help:"Cache mode" choices:"writeback|none|writethrought"`
|
||||
DISK string `help:"ID or name of disk to attach"`
|
||||
Driver string `help:"Disk driver" choices:"virtio|ide|scsi" mcp:"true"`
|
||||
Cache string `help:"Cache mode" choices:"writeback|none|writethrough" mcp:"true"`
|
||||
}
|
||||
R(&ServerAttachDiskOptions{}, "server-attach-disk", "Attach an existing virtual disks to a virtual server", func(s *mcclient.ClientSession, args *ServerAttachDiskOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
@@ -144,9 +138,11 @@ func init() {
|
||||
})
|
||||
|
||||
type ServerDetachDiskOptions struct {
|
||||
_ struct{} `mcp-desc:"从虚机卸载硬盘。SERVER/DISK 为 id/name;可选 delete-disk。先 climc_server_list / climc_disk_list"`
|
||||
|
||||
SERVER string `help:"ID or name of server"`
|
||||
DISK string `help:"ID or name of disk to detach"`
|
||||
DeleteDisk bool `help:"Delete disk if the disk not has flag of auto_delete when detached"`
|
||||
DeleteDisk bool `help:"Delete the disk after detach (ignored if disk has auto_delete)" mcp:"true"`
|
||||
}
|
||||
R(&ServerDetachDiskOptions{}, "server-detach-disk", "Detach a disk from a virtual server", func(s *mcclient.ClientSession, args *ServerDetachDiskOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
|
||||
@@ -23,15 +23,15 @@ import (
|
||||
|
||||
func init() {
|
||||
type ServerAttachDeviceOptions struct {
|
||||
SERVER string `help:"ID or name of server"`
|
||||
DEVICE string `help:"ID of isolated device to attach"`
|
||||
Type string `help:"Device type" choices:"GPU-HPC|GPU-VGA|PCI"`
|
||||
SERVER string `help:"ID or name of server"`
|
||||
DEVICE string `help:"ID of isolated device to attach"`
|
||||
GpuType string `help:"Type of GPU to attach to" choices:"HPC|VGA"`
|
||||
}
|
||||
R(&ServerAttachDeviceOptions{}, "server-attach-isolated-device", "Attach an existing isolated device to a virtual server", func(s *mcclient.ClientSession, args *ServerAttachDeviceOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
params.Add(jsonutils.NewString(args.DEVICE), "device")
|
||||
if len(args.Type) > 0 {
|
||||
params.Add(jsonutils.NewString(args.Type), "dev_type")
|
||||
if args.GpuType != "" {
|
||||
params.Add(jsonutils.NewString(args.GpuType), "gpu_type")
|
||||
}
|
||||
srv, err := modules.Servers.PerformAction(s, args.SERVER, "attach-isolated-device", params)
|
||||
if err != nil {
|
||||
@@ -44,11 +44,13 @@ func init() {
|
||||
type ServerDetachDeviceOptions struct {
|
||||
SERVER string `help:"ID or name of server"`
|
||||
DEVICE string `help:"ID of isolated device to detach"`
|
||||
INDEX int `help:"Index of isolated device to detach"`
|
||||
IsForce bool `help:"Force detach isolated device"`
|
||||
}
|
||||
R(&ServerDetachDeviceOptions{}, "server-detach-isolated-device", "Detach a isolated device from a virtual server", func(s *mcclient.ClientSession, args *ServerDetachDeviceOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
params.Add(jsonutils.NewString(args.DEVICE), "device")
|
||||
params.Add(jsonutils.NewInt(int64(args.INDEX)), "index")
|
||||
if args.IsForce {
|
||||
params.Set("is_force", jsonutils.JSONTrue)
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func init() {
|
||||
|
||||
type ServerNetworkUpdateOptions struct {
|
||||
SERVER string `help:"ID or Name of Server"`
|
||||
NETWORK string `help:"ID or Name of Wire"`
|
||||
NETWORK string `help:"ID or Name of Network"`
|
||||
Mac string `help:"Mac of NIC"`
|
||||
Driver string `help:"Driver model of vNIC" choices:"virtio|e1000|vmxnet3|rtl8139"`
|
||||
Index int64 `help:"Index of NIC" default:"-1"`
|
||||
@@ -205,7 +205,7 @@ func init() {
|
||||
Reserve bool `help:"Put the release IP address into reserved address pool"`
|
||||
Force bool `help:"detach server network by force"`
|
||||
}
|
||||
R(&ServerDetachNetworkOptions{}, "server-detach-network", "Detach the virtual network fron a virtual server", func(s *mcclient.ClientSession, args *ServerDetachNetworkOptions) error {
|
||||
R(&ServerDetachNetworkOptions{}, "server-detach-network", "Detach the virtual network from a virtual server", func(s *mcclient.ClientSession, args *ServerDetachNetworkOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
// params.Add(jsonutils.NewString(args.NETWORK), "net_id")
|
||||
if args.Reserve {
|
||||
|
||||
@@ -127,8 +127,9 @@ func init() {
|
||||
cmd.Perform("update-sub-ips", &options.ServerUpdateSubIpsOptions{})
|
||||
cmd.BatchPerform("restore-virtual-isolated-devices", &options.ServerIdsOptions{})
|
||||
cmd.BatchPerform("set-os-info", &options.ServerSetOSInfoOptions{})
|
||||
cmd.BatchPerform("start-rescue", &options.ServerStartOptions{})
|
||||
cmd.BatchPerform("stop-rescue", &options.ServerStartOptions{})
|
||||
// 与 server-start 复用参数时单独包一层,避免继承 mcp-desc 被注册为 MCP tool
|
||||
cmd.BatchPerform("start-rescue", &options.ServerStartRescueOptions{})
|
||||
cmd.BatchPerform("stop-rescue", &options.ServerStopRescueOptions{})
|
||||
cmd.BatchPerform("sync-os-info", &options.ServerIdsOptions{})
|
||||
cmd.BatchPerform("set-root-disk-matcher", &options.ServerSetRootDiskMatcher{})
|
||||
cmd.Perform("disable-auto-merge-snapshot", &options.ServerDisableAutoMergeSnapshot{})
|
||||
@@ -293,7 +294,7 @@ func init() {
|
||||
|
||||
type ServerDiskSnapshotOptions struct {
|
||||
SERVER string `help:"server ID or Name"`
|
||||
DISK string `help:"create snapshot disk id"`
|
||||
DISK string `help:"Disk ID for snapshot creation"`
|
||||
SNAPSHOTNAME string `help:"Snapshot name"`
|
||||
}
|
||||
R(&ServerDiskSnapshotOptions{}, "server-disk-create-snapshot", "Task server disk snapshot", func(s *mcclient.ClientSession, args *ServerDiskSnapshotOptions) error {
|
||||
@@ -733,7 +734,7 @@ func init() {
|
||||
|
||||
type ServerSnapshotAndClone struct {
|
||||
ID string `help:"ID or name of VM" json:"-"`
|
||||
NAME string `help:"Newly instance name" json:"name"`
|
||||
NAME string `help:"New instance name" json:"name"`
|
||||
AutoStart bool `help:"Auto start new guest"`
|
||||
AllowDelete bool `help:"Allow new guest delete" json:"-"`
|
||||
Count int `help:"Guest count"`
|
||||
@@ -772,7 +773,7 @@ func init() {
|
||||
ID string `help:"ID or name of server"`
|
||||
Save string `help:"save xml into this file"`
|
||||
}
|
||||
R(&ServerJnlpOptions{}, "server-jnlp", "Get baremetal server jnlp file contentn", func(s *mcclient.ClientSession, args *ServerJnlpOptions) error {
|
||||
R(&ServerJnlpOptions{}, "server-jnlp", "Get baremetal server jnlp file content", func(s *mcclient.ClientSession, args *ServerJnlpOptions) error {
|
||||
spec, err := modules.Servers.GetSpecific(s, args.ID, "jnlp", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -32,7 +32,7 @@ func init() {
|
||||
HostType string `help:"Host type filter" choices:"baremetal|hypervisor|esxi|kubelet|hyperv"`
|
||||
Gpu bool `help:"Only show gpu devices"`
|
||||
Zone string `help:"Filter by zone id or name"`
|
||||
Occupied bool `help:"show occupid host" json:"-"`
|
||||
Occupied bool `help:"show occupied host" json:"-"`
|
||||
}
|
||||
R(&ListOptions{}, "spec", "List all kinds of model specs", func(s *mcclient.ClientSession, args *ListOptions) error {
|
||||
var params *jsonutils.JSONDict
|
||||
@@ -76,7 +76,7 @@ func init() {
|
||||
DiskSpec []string `help:"Disk spec string, like 'Linux_adapter0_HDD_111Gx4'"`
|
||||
Nic int64 `help:"#Nics count of host" metavar:"<NIC_COUNT>"`
|
||||
GpuModel []string `help:"GPU model, like 'GeForce GTX 1050 Ti'"`
|
||||
Occupied bool `help:"Show occupid host" json:"-"`
|
||||
Occupied bool `help:"Show occupied host" json:"-"`
|
||||
Manufacture string `help:"Manufacture of host"`
|
||||
Model string `help:"Model of host"`
|
||||
}
|
||||
|
||||
@@ -25,15 +25,15 @@ import (
|
||||
)
|
||||
|
||||
type BaseEventListOptions struct {
|
||||
Scope string `help:"scope" choices:"project|domain|system"`
|
||||
Scope string `help:"RBAC scope" choices:"project|domain|system"`
|
||||
Since string `help:"Show logs since specific date" metavar:"DATETIME"`
|
||||
Until string `help:"Show logs until specific date" metavar:"DATETIME"`
|
||||
Limit int64 `help:"Limit number of logs" default:"20"`
|
||||
Offset int64 `help:"Offset"`
|
||||
Offset int64 `help:"Offset for pagination"`
|
||||
Ascending bool `help:"Ascending order"`
|
||||
Descending bool `help:"Descending order"`
|
||||
OrderBy string `help:"order by specific field"`
|
||||
Action []string `help:"Log action"`
|
||||
Action []string `help:"Filter by log action"`
|
||||
|
||||
User string `help:"filter by operator user"`
|
||||
Project string `help:"filter by operator user's project"`
|
||||
|
||||
@@ -22,9 +22,9 @@ import (
|
||||
|
||||
var (
|
||||
R = shell.R
|
||||
printList = printutils.PrintJSONList
|
||||
printObject = printutils.PrintJSONObject
|
||||
printBatchResults = printutils.PrintJSONBatchResults
|
||||
printList = shell.PrintList
|
||||
printObject = shell.PrintObject
|
||||
printBatchResults = shell.PrintBatchResults
|
||||
|
||||
InvalidUpdateError = shell.InvalidUpdateError
|
||||
printObjectRecursive = printutils.PrintJSONObjectRecursive
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
|
||||
func init() {
|
||||
type CredentialListOptions struct {
|
||||
Scope string `help:"scope" choices:"project|domain|system"`
|
||||
Scope string `help:"RBAC scope" choices:"project|domain|system"`
|
||||
Type string `help:"credential type" choices:"totp|recovery_secret|aksk|enc_key|container_image"`
|
||||
User string `help:"filter by user"`
|
||||
UserDomain string `help:"the domain of user"`
|
||||
|
||||
@@ -32,10 +32,12 @@ func init() {
|
||||
cmd.GetProperty(&identity_options.DomainGetPropertyTagValuePairOptions{})
|
||||
cmd.GetProperty(&identity_options.DomainGetPropertyTagValueTreeOptions{})
|
||||
|
||||
type DomainDetailOptions struct {
|
||||
type DomainShowOptions struct {
|
||||
_ struct{} `mcp-desc:"查询域详情。ID 可用 climc_domain_list 返回的 id/name"`
|
||||
|
||||
ID string `help:"ID or domain"`
|
||||
}
|
||||
R(&DomainDetailOptions{}, "domain-show", "Show detail of domain", func(s *mcclient.ClientSession, args *DomainDetailOptions) error {
|
||||
R(&DomainShowOptions{}, "domain-show", "Show detail of domain", func(s *mcclient.ClientSession, args *DomainShowOptions) error {
|
||||
result, err := modules.Domains.Get(s, args.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -43,7 +45,12 @@ func init() {
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
R(&DomainDetailOptions{}, "domain-delete", "Delete a domain", func(s *mcclient.ClientSession, args *DomainDetailOptions) error {
|
||||
type DomainDeleteOptions struct {
|
||||
_ struct{} `mcp-desc:"删除域。若尚不知 id,先用 climc_domain_list 定位;确认域下无项目/用户后再删"`
|
||||
|
||||
ID string `help:"ID or domain"`
|
||||
}
|
||||
R(&DomainDeleteOptions{}, "domain-delete", "Delete a domain", func(s *mcclient.ClientSession, args *DomainDeleteOptions) error {
|
||||
objId, err := modules.Domains.GetId(s, args.ID, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -72,12 +79,14 @@ func init() {
|
||||
}) */
|
||||
|
||||
type DomainCreateOptions struct {
|
||||
NAME string `help:"Name of domain"`
|
||||
Desc string `help:"Description"`
|
||||
Enabled bool `help:"Set the domain enabled"`
|
||||
Disabled bool `help:"Set the domain disabled"`
|
||||
_ struct{} `mcp-desc:"创建域。NAME 必填;可选 displayname/desc/enabled"`
|
||||
|
||||
Displayname string `help:"display name"`
|
||||
NAME string `help:"Name of domain"`
|
||||
Desc string `help:"Description" mcp:"true"`
|
||||
Enabled bool `help:"Set the domain enabled" mcp:"true"`
|
||||
Disabled bool `help:"Set the domain disabled" mcp:"true"`
|
||||
|
||||
Displayname string `help:"display name" mcp:"true"`
|
||||
}
|
||||
R(&DomainCreateOptions{}, "domain-create", "Create a new domain", func(s *mcclient.ClientSession, args *DomainCreateOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
|
||||
@@ -43,8 +43,10 @@ func init() {
|
||||
cmd.PerformClass("clean", &identity_options.ProjectCleanOptions{})
|
||||
|
||||
type ProjectShowOptions struct {
|
||||
_ struct{} `mcp-desc:"查询项目详情。ID 可用 climc_project_list 返回的 id/name;跨域时可传 domain"`
|
||||
|
||||
ID string `help:"ID or Name of project"`
|
||||
Domain string `help:"Domain"`
|
||||
Domain string `help:"Domain" mcp:"true"`
|
||||
}
|
||||
R(&ProjectShowOptions{}, "project-show", "Show details of project", func(s *mcclient.ClientSession, args *ProjectShowOptions) error {
|
||||
query := jsonutils.NewDict()
|
||||
@@ -62,7 +64,13 @@ func init() {
|
||||
printObject(result)
|
||||
return nil
|
||||
})
|
||||
R(&ProjectShowOptions{}, "project-delete", "Delete a project", func(s *mcclient.ClientSession, args *ProjectShowOptions) error {
|
||||
type ProjectDeleteOptions struct {
|
||||
_ struct{} `mcp-desc:"删除项目。若尚不知 id,先用 climc_project_list 定位;跨域时可传 domain"`
|
||||
|
||||
ID string `help:"ID or Name of project"`
|
||||
Domain string `help:"Domain" mcp:"true"`
|
||||
}
|
||||
R(&ProjectDeleteOptions{}, "project-delete", "Delete a project", func(s *mcclient.ClientSession, args *ProjectDeleteOptions) error {
|
||||
query := jsonutils.NewDict()
|
||||
if len(args.Domain) > 0 {
|
||||
domainId, err := modules.Domains.GetId(s, args.Domain, nil)
|
||||
@@ -83,12 +91,14 @@ func init() {
|
||||
})
|
||||
|
||||
type ProjectCreateOptions struct {
|
||||
_ struct{} `mcp-desc:"创建项目。NAME 必填;可选 domain/displayname/desc"`
|
||||
|
||||
NAME string `help:"Name of new project"`
|
||||
Displayname string `help:"display name"`
|
||||
Domain string `help:"Domain"`
|
||||
Desc string `help:"Description"`
|
||||
Enabled bool `help:"Project is enabled"`
|
||||
Disabled bool `help:"Project is disabled"`
|
||||
Displayname string `help:"display name" mcp:"true"`
|
||||
Domain string `help:"Domain" mcp:"true"`
|
||||
Desc string `help:"Description" mcp:"true"`
|
||||
Enabled bool `help:"Project is enabled" mcp:"true"`
|
||||
Disabled bool `help:"Project is disabled" mcp:"true"`
|
||||
}
|
||||
R(&ProjectCreateOptions{}, "project-create", "Create a project", func(s *mcclient.ClientSession, args *ProjectCreateOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
|
||||
@@ -37,11 +37,11 @@ func init() {
|
||||
RoleDomain string `help:"Domain for role"`
|
||||
Limit int64 `help:"maximal returned number of rows"`
|
||||
Offset int64 `help:"offset index of returned results"`
|
||||
Users []string `help:"fitler by user id or name"`
|
||||
Groups []string `help:"fitler by group id or name"`
|
||||
Roles []string `help:"fitler by role id or name"`
|
||||
Projects []string `help:"fitler by project id or name"`
|
||||
Domains []string `help:"fitler by domain id or name"`
|
||||
Users []string `help:"filter by user id or name"`
|
||||
Groups []string `help:"filter by group id or name"`
|
||||
Roles []string `help:"filter by role id or name"`
|
||||
Projects []string `help:"filter by project id or name"`
|
||||
Domains []string `help:"filter by domain id or name"`
|
||||
|
||||
ProjectDomainId string
|
||||
ProjectDomains []string `help:"filter by project's domain id or name"`
|
||||
|
||||
@@ -64,12 +64,14 @@ func init() {
|
||||
return nil
|
||||
})*/
|
||||
|
||||
type UserDetailOptions struct {
|
||||
type UserShowOptions struct {
|
||||
_ struct{} `mcp-desc:"查询用户详情。ID 可用 climc_user_list 返回的 id/name;跨域时可传 domain"`
|
||||
|
||||
ID string `help:"ID of user"`
|
||||
Domain string `help:"Domain"`
|
||||
System bool `help:"show system user"`
|
||||
Domain string `help:"Domain" mcp:"true"`
|
||||
System bool `help:"show system user" mcp:"true"`
|
||||
}
|
||||
R(&UserDetailOptions{}, "user-show", "Show details of user", func(s *mcclient.ClientSession, args *UserDetailOptions) error {
|
||||
R(&UserShowOptions{}, "user-show", "Show details of user", func(s *mcclient.ClientSession, args *UserShowOptions) error {
|
||||
query := jsonutils.NewDict()
|
||||
if len(args.Domain) > 0 {
|
||||
domainId, err := modules.Domains.GetId(s, args.Domain, nil)
|
||||
@@ -89,7 +91,14 @@ func init() {
|
||||
return nil
|
||||
})
|
||||
|
||||
R(&UserDetailOptions{}, "user-delete", "Delete user", func(s *mcclient.ClientSession, args *UserDetailOptions) error {
|
||||
type UserDeleteOptions struct {
|
||||
_ struct{} `mcp-desc:"删除用户。若尚不知 id,先用 climc_user_list 定位;跨域时可传 domain"`
|
||||
|
||||
ID string `help:"ID of user"`
|
||||
Domain string `help:"Domain" mcp:"true"`
|
||||
System bool `help:"show system user" mcp:"true"`
|
||||
}
|
||||
R(&UserDeleteOptions{}, "user-delete", "Delete user", func(s *mcclient.ClientSession, args *UserDeleteOptions) error {
|
||||
query := jsonutils.NewDict()
|
||||
if len(args.Domain) > 0 {
|
||||
domainId, err := modules.Domains.GetId(s, args.Domain, nil)
|
||||
@@ -109,6 +118,11 @@ func init() {
|
||||
return nil
|
||||
})
|
||||
|
||||
type UserDetailOptions struct {
|
||||
ID string `help:"ID of user"`
|
||||
Domain string `help:"Domain"`
|
||||
System bool `help:"show system user"`
|
||||
}
|
||||
R(&UserDetailOptions{}, "user-project-list", "List projects of user", func(s *mcclient.ClientSession, args *UserDetailOptions) error {
|
||||
query := jsonutils.NewDict()
|
||||
if len(args.Domain) > 0 {
|
||||
@@ -170,29 +184,31 @@ func init() {
|
||||
})
|
||||
|
||||
type UserCreateOptions struct {
|
||||
NAME string `help:"Name of the new user"`
|
||||
Domain string `help:"Domain"`
|
||||
Desc string `help:"Description"`
|
||||
Password *string `help:"Password"`
|
||||
Displayname string `help:"Displayname"`
|
||||
Email string `help:"Email"`
|
||||
Mobile string `help:"Mobile"`
|
||||
Enabled bool `help:"Enabled"`
|
||||
Disabled bool `help:"Disabled"`
|
||||
_ struct{} `mcp-desc:"创建用户。NAME 必填;建议传 password;可选 domain/email/mobile/displayname"`
|
||||
|
||||
SkipPasswordComplexityCheck bool `help:"do password complexity check, default is false"`
|
||||
NAME string `help:"Name of the new user"`
|
||||
Domain string `help:"Domain" mcp:"true"`
|
||||
Desc string `help:"Description" mcp:"true"`
|
||||
Password *string `help:"Password" mcp:"true"`
|
||||
Displayname string `help:"Displayname" mcp:"true"`
|
||||
Email string `help:"Email" mcp:"true"`
|
||||
Mobile string `help:"Mobile" mcp:"true"`
|
||||
Enabled bool `help:"Enabled" mcp:"true"`
|
||||
Disabled bool `help:"Disabled" mcp:"true"`
|
||||
|
||||
SkipPasswordComplexityCheck bool `help:"skip password complexity check" mcp:"true"`
|
||||
|
||||
// DefaultProject string `help:"Default project"`
|
||||
SystemAccount bool `help:"is a system account?"`
|
||||
NoWebConsole bool `help:"allow web console access"`
|
||||
EnableMfa bool `help:"enable TOTP mfa"`
|
||||
SystemAccount bool `help:"is a system account?" mcp:"true"`
|
||||
NoWebConsole bool `help:"disallow web console access" mcp:"true"`
|
||||
EnableMfa bool `help:"enable TOTP mfa" mcp:"true"`
|
||||
|
||||
IdpId string `help:"Id of identity provider to link with"`
|
||||
IdpEntityId string `help:"Entity id of identity provider to link with"`
|
||||
IdpId string `help:"Id of identity provider to link with" mcp:"true"`
|
||||
IdpEntityId string `help:"Entity id of identity provider to link with" mcp:"true"`
|
||||
|
||||
Lang string `help:"user default language"`
|
||||
Lang string `help:"user default language" mcp:"true"`
|
||||
|
||||
Expire string `help:"user expired at"`
|
||||
Expire string `help:"user expired at" mcp:"true"`
|
||||
}
|
||||
R(&UserCreateOptions{}, "user-create", "Create a user", func(s *mcclient.ClientSession, args *UserCreateOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
|
||||
@@ -22,9 +22,9 @@ import (
|
||||
|
||||
var (
|
||||
R = shell.R
|
||||
printList = printutils.PrintJSONList
|
||||
printObject = printutils.PrintJSONObject
|
||||
printBatchResults = printutils.PrintJSONBatchResults
|
||||
printList = shell.PrintList
|
||||
printObject = shell.PrintObject
|
||||
printBatchResults = shell.PrintBatchResults
|
||||
|
||||
InvalidUpdateError = shell.InvalidUpdateError
|
||||
printObjectRecursive = printutils.PrintJSONObjectRecursive
|
||||
|
||||
@@ -56,10 +56,10 @@ type ImageOptionalOptions struct {
|
||||
OsCodename string `help:"Codename of OS"`
|
||||
OsArch string `help:"Os hardware architecture" choices:"x86|x86_64|aarch32|aarch64|riscv32|riscv64"`
|
||||
OsLang string `help:"OS Language" choices:"zh_CN|en_US"`
|
||||
Preference int64 `help:"Disk preferences"`
|
||||
Preference int64 `help:"Image disk preference weight"`
|
||||
Notes string `help:"Notes about the image"`
|
||||
Hypervisor []string `help:"Prefer hypervisor type" choices:"kvm|esxi|baremetal|container|openstack|ctyun"`
|
||||
DiskDriver string `help:"Perfer disk driver" choices:"virtio|scsi|pvscsi|ide|sata"`
|
||||
DiskDriver string `help:"Prefer disk driver" choices:"virtio|scsi|pvscsi|ide|sata"`
|
||||
NetDriver string `help:"Preferred network driver" choices:"virtio|e1000|vmxnet3"`
|
||||
DisableUsbKbd bool `help:"Disable usb keyboard on this image(for hypervisor kvm)"`
|
||||
BootMode string `help:"UEFI support" choices:"UEFI|BIOS"`
|
||||
|
||||
@@ -24,15 +24,15 @@ import (
|
||||
)
|
||||
|
||||
type BaseActionListOptions struct {
|
||||
Scope string `help:"scope" choices:"project|domain|system"`
|
||||
Scope string `help:"RBAC scope" choices:"project|domain|system"`
|
||||
Since string `help:"Show logs since specific date" metavar:"DATETIME"`
|
||||
Until string `help:"Show logs until specific date" metavar:"DATETIME"`
|
||||
Limit int64 `help:"Limit number of logs" default:"20"`
|
||||
Offset int64 `help:"Offset"`
|
||||
Offset int64 `help:"Offset for pagination"`
|
||||
Ascending bool `help:"Ascending order"`
|
||||
Descending bool `help:"Descending order"`
|
||||
Field []string `help:"field options"`
|
||||
Action []string `help:"Log action"`
|
||||
Field []string `help:"Fields to return"`
|
||||
Action []string `help:"Filter by log action"`
|
||||
Search string `help:"Filter action logs by obj_name, using 'like' syntax."`
|
||||
Admin bool `help:"admin mode"`
|
||||
Succ bool `help:"Show success action log only"`
|
||||
@@ -47,15 +47,17 @@ type BaseActionListOptions struct {
|
||||
}
|
||||
|
||||
type ActionListOptions struct {
|
||||
_ struct{} `mcp-desc:"查询操作日志(谁在何时对资源做了什么)。可按对象类型 type(如 server/disk/host)、对象 id、时间 since/until、action、user、project、succ/fail 过滤;默认 limit=20。排查创建失败、误操作、审计时优先调用"`
|
||||
|
||||
BaseActionListOptions
|
||||
Service []string `help:"service name"`
|
||||
Id string `help:"" metavar:"OBJ_ID"`
|
||||
Id string `help:"filter by object id" metavar:"OBJ_ID"`
|
||||
Type []string `help:"Type of relevant object" metavar:"OBJ_TYPE"`
|
||||
}
|
||||
|
||||
type TypeActionListOptions struct {
|
||||
BaseActionListOptions
|
||||
ID string `help:"" metavar:"OBJ_ID"`
|
||||
ID string `help:"Object ID" metavar:"OBJ_ID"`
|
||||
}
|
||||
|
||||
func doActionList(s *mcclient.ClientSession, args *ActionListOptions) error {
|
||||
|
||||
@@ -15,13 +15,11 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"yunion.io/x/pkg/util/printutils"
|
||||
|
||||
"yunion.io/x/onecloud/cmd/climc/shell"
|
||||
)
|
||||
|
||||
var (
|
||||
R = shell.R
|
||||
printList = printutils.PrintJSONList
|
||||
printObject = printutils.PrintJSONObject
|
||||
printList = shell.PrintList
|
||||
printObject = shell.PrintObject
|
||||
)
|
||||
|
||||
@@ -15,17 +15,15 @@
|
||||
package monitor
|
||||
|
||||
import (
|
||||
"yunion.io/x/pkg/util/printutils"
|
||||
|
||||
"yunion.io/x/onecloud/cmd/climc/shell"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
|
||||
)
|
||||
|
||||
var (
|
||||
R = shell.R
|
||||
printList = printutils.PrintJSONList
|
||||
printObject = printutils.PrintJSONObject
|
||||
printBatchResults = printutils.PrintJSONBatchResults
|
||||
printList = shell.PrintList
|
||||
printObject = shell.PrintObject
|
||||
printBatchResults = shell.PrintBatchResults
|
||||
)
|
||||
|
||||
func NewResourceCmd(manager modulebase.IBaseManager) *shell.ResourceCmd {
|
||||
|
||||
@@ -126,7 +126,7 @@ func printQuotaList(result jsonutils.JSONObject) {
|
||||
|
||||
func init() {
|
||||
type QuotaOptions struct {
|
||||
Scope string `help:"scope" choices:"domain|project"`
|
||||
Scope string `help:"RBAC scope" choices:"domain|project"`
|
||||
Project string `help:"Tenant name or ID" json:"tenant"`
|
||||
Domain string `help:"Domain name or ID" json:"domain"`
|
||||
Refresh bool `help:"refresh" json:"refresh,omitfalse"`
|
||||
@@ -413,7 +413,7 @@ func init() {
|
||||
})
|
||||
|
||||
type CleanPendingUsageOptions struct {
|
||||
Scope string `help:"scope" choices:"domain|project"`
|
||||
Scope string `help:"RBAC scope" choices:"domain|project"`
|
||||
Project string `help:"Tenant name or ID" json:"tenant"`
|
||||
Domain string `help:"Domain name or ID" json:"domain"`
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ import (
|
||||
|
||||
var (
|
||||
R = shell.R
|
||||
printList = printutils.PrintJSONList
|
||||
printObject = printutils.PrintJSONObject
|
||||
printBatchResults = printutils.PrintJSONBatchResults
|
||||
printList = shell.PrintList
|
||||
printObject = shell.PrintObject
|
||||
printBatchResults = shell.PrintBatchResults
|
||||
|
||||
InvalidUpdateError = shell.InvalidUpdateError
|
||||
printObjectRecursive = printutils.PrintJSONObjectRecursive
|
||||
|
||||
@@ -15,10 +15,15 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/util/printutils"
|
||||
@@ -37,45 +42,111 @@ const (
|
||||
|
||||
var outputFormat = OUTPUT_FORMAT_TABLE
|
||||
|
||||
// goroutine 本地输出:MCP 并发 tools/call 时避免劫持全局 os.Stdout。
|
||||
type outputState struct {
|
||||
writer io.Writer
|
||||
format string
|
||||
}
|
||||
|
||||
var outputStates sync.Map // uint64(goid) -> *outputState
|
||||
|
||||
func OutputFormat(s string) {
|
||||
outputFormat = s
|
||||
}
|
||||
|
||||
// PushOutput 将当前 goroutine 的 shell 输出重定向到 w,并可选覆盖格式。
|
||||
// 返回的 restore 必须在同一 goroutine 调用。
|
||||
func PushOutput(w io.Writer, format string) (restore func()) {
|
||||
id := goroutineID()
|
||||
prev, _ := outputStates.Load(id)
|
||||
outputStates.Store(id, &outputState{writer: w, format: format})
|
||||
return func() {
|
||||
if prev != nil {
|
||||
outputStates.Store(id, prev)
|
||||
} else {
|
||||
outputStates.Delete(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func currentWriter() io.Writer {
|
||||
if v, ok := outputStates.Load(goroutineID()); ok {
|
||||
if s := v.(*outputState); s != nil && s.writer != nil {
|
||||
return s.writer
|
||||
}
|
||||
}
|
||||
return os.Stdout
|
||||
}
|
||||
|
||||
func currentFormat() string {
|
||||
if v, ok := outputStates.Load(goroutineID()); ok {
|
||||
if s := v.(*outputState); s != nil && s.format != "" {
|
||||
return s.format
|
||||
}
|
||||
}
|
||||
return outputFormat
|
||||
}
|
||||
|
||||
func goroutineID() uint64 {
|
||||
b := make([]byte, 64)
|
||||
b = b[:runtime.Stack(b, false)]
|
||||
b = bytes.TrimPrefix(b, []byte("goroutine "))
|
||||
i := bytes.IndexByte(b, ' ')
|
||||
if i <= 0 {
|
||||
return 0
|
||||
}
|
||||
n, _ := strconv.ParseUint(string(b[:i]), 10, 64)
|
||||
return n
|
||||
}
|
||||
|
||||
func PrintList(list *printutils.ListResult, columns []string) {
|
||||
switch outputFormat {
|
||||
w := currentWriter()
|
||||
switch currentFormat() {
|
||||
case OUTPUT_FORMAT_TABLE:
|
||||
printutils.PrintJSONList(list, columns)
|
||||
if w == os.Stdout {
|
||||
printutils.PrintJSONList(list, columns)
|
||||
return
|
||||
}
|
||||
fmt.Fprint(w, jsonutils.Marshal(list).PrettyString())
|
||||
fmt.Fprint(w, "\n")
|
||||
case OUTPUT_FORMAT_JSON:
|
||||
fmt.Print(jsonutils.Marshal(list).PrettyString())
|
||||
fmt.Print("\n")
|
||||
fmt.Fprint(w, jsonutils.Marshal(list).PrettyString())
|
||||
fmt.Fprint(w, "\n")
|
||||
case OUTPUT_FORMAT_YAML:
|
||||
fmt.Print(jsonutils.Marshal(list).YAMLString())
|
||||
fmt.Fprint(w, jsonutils.Marshal(list).YAMLString())
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown output format: %q\n", outputFormat)
|
||||
fmt.Fprintf(os.Stderr, "unknown output format: %q\n", currentFormat())
|
||||
}
|
||||
}
|
||||
|
||||
func PrintObject(obj jsonutils.JSONObject) {
|
||||
switch outputFormat {
|
||||
w := currentWriter()
|
||||
switch currentFormat() {
|
||||
case OUTPUT_FORMAT_TABLE:
|
||||
printutils.PrintJSONObject(obj)
|
||||
if w == os.Stdout {
|
||||
printutils.PrintJSONObject(obj)
|
||||
return
|
||||
}
|
||||
fmt.Fprint(w, obj.PrettyString())
|
||||
fmt.Fprint(w, "\n")
|
||||
case OUTPUT_FORMAT_KV:
|
||||
printObjectFmtKv(obj)
|
||||
case OUTPUT_FORMAT_JSON:
|
||||
fmt.Print(obj.PrettyString())
|
||||
fmt.Print("\n")
|
||||
fmt.Fprint(w, obj.PrettyString())
|
||||
fmt.Fprint(w, "\n")
|
||||
case OUTPUT_FORMAT_YAML:
|
||||
fmt.Print(obj.YAMLString())
|
||||
fmt.Fprint(w, obj.YAMLString())
|
||||
case OUTPUT_FORMAT_FLATTEN_TABLE:
|
||||
printObjectRecursive(obj)
|
||||
case OUTPUT_FORMAT_FLATTEN_KV:
|
||||
printObjectRecursiveEx(obj, printObjectFmtKv)
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "unknown output format: %q\n", outputFormat)
|
||||
fmt.Fprintf(os.Stderr, "unknown output format: %q\n", currentFormat())
|
||||
}
|
||||
}
|
||||
|
||||
func printObjectFmtKv(obj jsonutils.JSONObject) {
|
||||
w := currentWriter()
|
||||
m, _ := obj.GetMap()
|
||||
maxWidth := 0
|
||||
keys := make([]string, 0, len(m))
|
||||
@@ -95,7 +166,7 @@ func printObjectFmtKv(obj jsonutils.JSONObject) {
|
||||
} else {
|
||||
s = objV.String()
|
||||
}
|
||||
fmt.Printf("%*s: %s\n", maxWidth, k, s)
|
||||
fmt.Fprintf(w, "%*s: %s\n", maxWidth, k, s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,8 +178,24 @@ func printObjectRecursiveEx(obj jsonutils.JSONObject, cb printutils.PrintJSONObj
|
||||
printutils.PrintJSONObjectRecursiveEx(obj, cb)
|
||||
}
|
||||
|
||||
func PrintBatchResults(results []printutils.SubmitResult, columns []string) {
|
||||
w := currentWriter()
|
||||
switch currentFormat() {
|
||||
case OUTPUT_FORMAT_JSON:
|
||||
fmt.Fprint(w, jsonutils.Marshal(results).PrettyString())
|
||||
fmt.Fprint(w, "\n")
|
||||
default:
|
||||
if w == os.Stdout {
|
||||
printutils.PrintJSONBatchResults(results, columns)
|
||||
return
|
||||
}
|
||||
fmt.Fprint(w, jsonutils.Marshal(results).PrettyString())
|
||||
fmt.Fprint(w, "\n")
|
||||
}
|
||||
}
|
||||
|
||||
func printBatchResults(results []printutils.SubmitResult, columns []string) {
|
||||
printutils.PrintJSONBatchResults(results, columns)
|
||||
PrintBatchResults(results, columns)
|
||||
}
|
||||
|
||||
func ExportList(list *printutils.ListResult, file string, exportKeys string, exportTexts string, columns []string) {
|
||||
|
||||
@@ -32,10 +32,10 @@ import (
|
||||
|
||||
func init() {
|
||||
type ParametersListOptions struct {
|
||||
Name string `help:"List parameter of specificated name"`
|
||||
NamespaceId string `help:"List parameter of specificated namespace id, ADMIN only"`
|
||||
User string `help:"List parameter of specificated user id, ADMIN only" token:"user-id"`
|
||||
Service string `help:"List parameter of specificated service id, ADMIN only"`
|
||||
Name string `help:"List parameter of specified name"`
|
||||
NamespaceId string `help:"List parameter of specified namespace id, ADMIN only"`
|
||||
User string `help:"List parameter of specified user id, ADMIN only" token:"user-id"`
|
||||
Service string `help:"List parameter of specified service id, ADMIN only"`
|
||||
options.BaseListOptions
|
||||
}
|
||||
|
||||
@@ -72,9 +72,9 @@ func init() {
|
||||
})
|
||||
|
||||
type ParametersShowOptions struct {
|
||||
NamespaceId string `help:"Show parameter of specificated namespace id, ADMIN only"`
|
||||
User string `help:"Show parameter of specificated user id, ADMIN only"`
|
||||
Service string `help:"Show parameter of specificated service id, ADMIN only"`
|
||||
NamespaceId string `help:"Show parameter of specified namespace id, ADMIN only"`
|
||||
User string `help:"Show parameter of specified user id, ADMIN only"`
|
||||
Service string `help:"Show parameter of specified service id, ADMIN only"`
|
||||
NAME string `help:"The name of parameter"`
|
||||
}
|
||||
|
||||
@@ -109,8 +109,8 @@ func init() {
|
||||
})
|
||||
|
||||
type ParametersCreateOptions struct {
|
||||
User string `help:"Create parameter for specificated user id, ADMIN only"`
|
||||
Service string `help:"Create parameter for specificated service id, ADMIN only"`
|
||||
User string `help:"Create parameter for specified user id, ADMIN only"`
|
||||
Service string `help:"Create parameter for specified service id, ADMIN only"`
|
||||
NAME string `help:"The name of parameter"`
|
||||
VALUE string `help:"The content of parameter"`
|
||||
}
|
||||
@@ -140,9 +140,9 @@ func init() {
|
||||
})
|
||||
|
||||
type ParametersEditOptions struct {
|
||||
NamespaceId string `help:"List parameter of specificated namespace id, ADMIN only"`
|
||||
User string `help:"Update parameter of specificated user id, ADMIN only"`
|
||||
Service string `help:"Update parameter of specificated service id, ADMIN only"`
|
||||
NamespaceId string `help:"List parameter of specified namespace id, ADMIN only"`
|
||||
User string `help:"Update parameter of specified user id, ADMIN only"`
|
||||
Service string `help:"Update parameter of specified service id, ADMIN only"`
|
||||
NAME string `help:"The name of parameter"`
|
||||
}
|
||||
|
||||
@@ -222,8 +222,8 @@ func init() {
|
||||
})
|
||||
|
||||
type ParametersDeleteOptions struct {
|
||||
User string `help:"Delete parameter of specificated user id, ADMIN only"`
|
||||
Service string `help:"Delete parameter of specificated service id, ADMIN only"`
|
||||
User string `help:"Delete parameter of specified user id, ADMIN only"`
|
||||
Service string `help:"Delete parameter of specified service id, ADMIN only"`
|
||||
NAME string `help:"The name of parameter"`
|
||||
}
|
||||
|
||||
@@ -248,8 +248,8 @@ func init() {
|
||||
})
|
||||
|
||||
type ParameterCloneOptions struct {
|
||||
User string `help:"Clone parameter of specificated user id"`
|
||||
Service string `help:"Clone parameter of specificated service id"`
|
||||
User string `help:"Clone parameter of specified user id"`
|
||||
Service string `help:"Clone parameter of specified service id"`
|
||||
NAME string `help:"The name of parameter"`
|
||||
DestUser string `help:"destination user id of clone action"`
|
||||
DestService string `help:"destination service id of clone action"`
|
||||
|
||||
@@ -81,7 +81,7 @@ func newExecutor(options *BaseOptions) (ipmitool.IPMIExecutor, error) {
|
||||
if options.Port > 0 {
|
||||
port = options.Port
|
||||
}
|
||||
return ipmitool.NewLanPlusIPMIWithPort(options.HOST, options.User, options.PASSWD, port), nil
|
||||
return ipmitool.NewLanPlusIPMIWithPort(options.HOST, options.User, options.PASSWD, port)
|
||||
}
|
||||
return nil, fmt.Errorf("Unsupported mode: %s", options.MODE)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"yunion.io/x/pkg/util/signalutils"
|
||||
"yunion.io/x/pkg/utils"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/hostman/isolated_device"
|
||||
"yunion.io/x/onecloud/pkg/hostman/isolated_device/container_device"
|
||||
"yunion.io/x/onecloud/pkg/hostman/options"
|
||||
@@ -67,7 +68,7 @@ func NewDaemon() (*Daemon, error) {
|
||||
}
|
||||
for i := range devCfg.Devices {
|
||||
if devCfg.Devices[i].Type == isolated_device.ContainerDeviceTypeNvidiaMps {
|
||||
dev, err := container_device.NewPCIGPURenderBaseDevice(devCfg.Devices[i].Path, 0, isolated_device.ContainerDeviceTypeNvidiaMps)
|
||||
dev, err := container_device.NewPCIGPURenderBaseDevice(devCfg.Devices[i].Path, 0, api.GPU_TYPE, api.DEVICE_SHARING_MODE_MPS)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "parse pci device %s", devCfg.Devices[i].Path)
|
||||
}
|
||||
|
||||
@@ -1,59 +1,24 @@
|
||||
# MCP Server
|
||||
|
||||
MCP Server 是 Cloudpods 多云管理平台的核心组件之一,负责处理多云资源的统一管理和调度。
|
||||
Cloudpods MCP Server:通过 MCP 协议把 climc 能力暴露给 AI 客户端(Cursor / Claude 等)。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
├── adapters/ # 适配器模块,用于对接不同云平台的API
|
||||
├── config/ # 配置模块,处理服务配置和加载
|
||||
├── models/ # 数据模型,定义云资源的数据结构
|
||||
├── registry/ # 注册中心,管理可用的工具和服务
|
||||
├── server/ # 服务核心,包含服务启动和初始化逻辑
|
||||
└── tools/ # 工具模块,实现各种云资源管理功能
|
||||
├── adapters/ # Cloudpods 认证与 ClientSession
|
||||
├── climcgen/ # 从 climc CommandTable + Options tag 生成 MCP tools
|
||||
├── options/ # 服务配置
|
||||
├── registry/ # MCP tool 注册
|
||||
├── server/ # SSE / stdio 服务
|
||||
└── service/ # 进程入口装配
|
||||
```
|
||||
|
||||
## 架构设计
|
||||
|
||||
MCP Server 采用模块化设计,主要包括以下几个核心模块:
|
||||
|
||||
1. **适配器模块 (Adapters)**: 负责与不同云平台的API进行交互,实现资源的统一管理。
|
||||
2. **配置模块 (Config)**: 处理服务的配置加载和管理,支持多种配置方式。
|
||||
3. **数据模型 (Models)**: 定义云资源的数据结构,为其他模块提供统一的数据访问接口。
|
||||
4. **注册中心 (Registry)**: 管理可用的工具和服务,支持动态注册和发现。
|
||||
5. **服务核心 (Server)**: 负责服务的启动、初始化和生命周期管理。
|
||||
6. **工具模块 (Tools)**: 实现各种云资源管理功能,如VPC、网络、镜像等。
|
||||
|
||||
## 运行机制
|
||||
|
||||
1. 服务启动时,首先加载配置文件并初始化各个模块。
|
||||
2. 适配器模块根据配置连接到相应的云平台。
|
||||
3. 注册中心注册所有可用的工具和服务。
|
||||
4. 服务核心启动HTTP服务器,监听客户端请求。
|
||||
5. 客户端通过API调用相应的工具来管理云资源。
|
||||
1. 启动时 blank-import climc shell 包,填充 `shell.CommandTable`
|
||||
2. 扫描 Options 上带 `mcp-desc` 的命令,用 Options struct tag 生成 schema 并注册 tools
|
||||
3. 工具调用时用 AK/SK(或 Header)建 session,执行对应 climc callback,JSON 输出返回给客户端
|
||||
|
||||
## 主要功能
|
||||
## 扩展工具
|
||||
|
||||
- 统一管理多云资源(VPC、网络、镜像、主机等)
|
||||
- 支持多种云平台(AWS、Azure、阿里云等)
|
||||
- 提供RESTful API接口
|
||||
- 支持资源的查询、创建、更新和删除操作
|
||||
|
||||
## 配置说明
|
||||
|
||||
配置文件位于 `options/options.go`,主要包含以下配置项:
|
||||
|
||||
- ServerConfig: 服务配置,如监听地址、端口等
|
||||
- MCPConfig: MCP相关配置
|
||||
- ExternalConfig: 外部服务配置
|
||||
|
||||
## 开发指南
|
||||
|
||||
1. 实现新的云资源管理功能时,需要在 `tools/` 目录下创建相应的工具文件。
|
||||
2. 工具需要实现 `Tool` 接口,包括 `GetTool`、`Handle` 和 `GetName` 方法。
|
||||
3. 数据模型定义在 `models/` 目录下,需要根据云平台API文档进行定义。
|
||||
4. 适配器实现在 `adapters/` 目录下,用于与云平台API进行交互。
|
||||
|
||||
## 贡献
|
||||
|
||||
欢迎提交Issue和Pull Request来改进MCP Server。
|
||||
在对应 climc Options 上增加 `_ struct{} \`mcp-desc:"..."\``(并按需给字段加 `mcp:"true"`),重启 mcp-server 即可注册。
|
||||
|
||||
@@ -25,6 +25,12 @@ definitions:
|
||||
description: 权限Id
|
||||
type: string
|
||||
x-go-name: CloudpolicyId
|
||||
cloudpolicy_ids:
|
||||
description: 权限Id列表
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-go-name: CloudpolicyIds
|
||||
type: object
|
||||
x-go-package: yunion.io/x/onecloud/pkg/apis/cloudid
|
||||
CloudgroupCreateInput:
|
||||
@@ -108,6 +114,12 @@ definitions:
|
||||
description: 权限Id
|
||||
type: string
|
||||
x-go-name: CloudpolicyId
|
||||
cloudpolicy_ids:
|
||||
description: 权限Id列表
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-go-name: CloudpolicyIds
|
||||
type: object
|
||||
x-go-package: yunion.io/x/onecloud/pkg/apis/cloudid
|
||||
CloudgroupDetails:
|
||||
|
||||
15
go.mod
15
go.mod
@@ -9,6 +9,10 @@ require (
|
||||
github.com/Microsoft/go-winio v0.6.2
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.684
|
||||
github.com/anacrolix/torrent v1.57.0
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.16
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.4
|
||||
github.com/aws/smithy-go v1.24.2
|
||||
github.com/benbjohnson/clock v1.0.0
|
||||
github.com/bitly/go-simplejson v0.5.0
|
||||
github.com/c-bata/go-prompt v0.2.4
|
||||
@@ -90,6 +94,7 @@ require (
|
||||
golang.org/x/net v0.43.0
|
||||
golang.org/x/sync v0.16.0
|
||||
golang.org/x/sys v0.35.0
|
||||
golang.org/x/term v0.34.0
|
||||
golang.org/x/text v0.28.0
|
||||
golang.org/x/time v0.5.0
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028
|
||||
@@ -108,12 +113,12 @@ require (
|
||||
k8s.io/cri-api v0.28.15
|
||||
k8s.io/klog/v2 v2.90.1
|
||||
moul.io/http2curl/v2 v2.3.0
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260616113329-0b3b4fbed80a
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260722023554-952117f57446
|
||||
yunion.io/x/executor v0.0.0-20260312022053-f538abd2b005
|
||||
yunion.io/x/jsonutils v1.0.1-0.20260715075349-615cfb44ff7c
|
||||
yunion.io/x/log v1.0.1-0.20240305175729-7cf2d6cd5a91
|
||||
yunion.io/x/ovsdb v0.0.0-20230306173834-f164f413a900
|
||||
yunion.io/x/pkg v1.10.4-0.20260422030155-01b100134978
|
||||
yunion.io/x/pkg v1.10.4-0.20260720021919-5e34f63c5815
|
||||
yunion.io/x/s3cli v0.0.0-20241221171442-1c11599d28e1
|
||||
yunion.io/x/sqlchemy v1.1.3-0.20251231025938-b0a38f6e9fab
|
||||
yunion.io/x/structarg v0.0.0-20231017124457-df4d5009457c
|
||||
@@ -169,10 +174,8 @@ require (
|
||||
github.com/aokoli/goutils v1.0.1 // indirect
|
||||
github.com/apparentlymart/go-cidr v1.1.0 // indirect
|
||||
github.com/aws/aws-sdk-go v1.44.194 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
|
||||
@@ -182,12 +185,9 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/organizations v1.51.1-0.20260401181752-9074b3ddace9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 // indirect
|
||||
github.com/aws/smithy-go v1.24.2 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/basgys/goxml2json v1.1.1-0.20181031222924-996d9fc8d313 // indirect
|
||||
github.com/beevik/etree v1.1.0 // indirect
|
||||
@@ -403,7 +403,6 @@ require (
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||
golang.org/x/mod v0.26.0 // indirect
|
||||
golang.org/x/oauth2 v0.17.0 // indirect
|
||||
golang.org/x/term v0.34.0 // indirect
|
||||
golang.org/x/tools v0.35.0 // indirect
|
||||
google.golang.org/api v0.167.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
|
||||
12
go.sum
12
go.sum
@@ -230,8 +230,6 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 h1:5r34CgVOD
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9/go.mod h1:dB12CEbNWPbzO2uC6QSWHteqOg4JfBVJOojbAoAUb5I=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9 h1:wuZ5uW2uhJR63zwNlqWH2W4aL4ZjeJP3o92/W+odDY4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.9/go.mod h1:/G58M2fGszCrOzvJUkDdY8O9kycodunH4VdT5oBAqls=
|
||||
github.com/aws/aws-sdk-go-v2/service/organizations v1.51.1-0.20260401181752-9074b3ddace9 h1:qi11riT4zVjV41Nh74zbBgK0EQbkbZG8rPm4shDFlJk=
|
||||
github.com/aws/aws-sdk-go-v2/service/organizations v1.51.1-0.20260401181752-9074b3ddace9/go.mod h1:urLFj1twuR/h5T0wN/2/kmY1gxBFa1tTKr+c60lZ2fA=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.4 h1:mUI3b885qJgfqKDUSj6RgbRqLdX0wGmg8ruM03zNfQA=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.4/go.mod h1:6v8ukAxc7z4x4oBjGUsLnH7KGLY9Uhcgij19UJNkiMg=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 h1:A1oRkiSQOWstGh61y4Wc/yQ04sqrQZr1Si/oAXj20/s=
|
||||
@@ -1783,13 +1781,11 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260616113329-0b3b4fbed80a h1:6R6t1tPpU/aR61wj5zCSC95KMglfM1ES85nbTneZSrk=
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260616113329-0b3b4fbed80a/go.mod h1:rCz5Nds6Zi/5uaj51oRDYdj5fZ5uULmKbuBAgNLuI+w=
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260722023554-952117f57446 h1:qDbFA6dfB2uxmOIx0kGiJUMg+xt4TiooEKx3URaHbN8=
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260722023554-952117f57446/go.mod h1:DaGxV/EVSzixqiNcVwXMvCcNGcVf8K+GzmNIjnjtJS8=
|
||||
yunion.io/x/executor v0.0.0-20260312022053-f538abd2b005 h1:3sWwcjGXGjG9mLBWa7AyLq+QSi0udTAx21pfVQRFMBE=
|
||||
yunion.io/x/executor v0.0.0-20260312022053-f538abd2b005/go.mod h1:Uxuou9WQIeJXNpy7t2fPLL0BYLvLiMvGQwY7Qc6aSws=
|
||||
yunion.io/x/jsonutils v0.0.0-20190625054549-a964e1e8a051/go.mod h1:4N0/RVzsYL3kH3WE/H1BjUQdFiWu50JGCFQuuy+Z634=
|
||||
yunion.io/x/jsonutils v1.0.1-0.20250507052344-1abcf4f443b1 h1:/+THlvf/MvgCW+7KeCDCr33e81KSRa5JmdZ1IIyLOXQ=
|
||||
yunion.io/x/jsonutils v1.0.1-0.20250507052344-1abcf4f443b1/go.mod h1:VK4Z93dgiKgAijcSqbMKmGaBMJuHulR16Hz4K015ZPo=
|
||||
yunion.io/x/jsonutils v1.0.1-0.20260715075349-615cfb44ff7c h1:FthZM1upg4/yF4vmYheo/J7a738sGPjWPKk5kY88sH0=
|
||||
yunion.io/x/jsonutils v1.0.1-0.20260715075349-615cfb44ff7c/go.mod h1:VK4Z93dgiKgAijcSqbMKmGaBMJuHulR16Hz4K015ZPo=
|
||||
yunion.io/x/log v0.0.0-20190514041436-04ce53b17c6b/go.mod h1:+gauLs73omeJAPlsXcevLsJLKixV+sR/E7WSYTSx1fE=
|
||||
@@ -1800,8 +1796,8 @@ yunion.io/x/ovsdb v0.0.0-20230306173834-f164f413a900 h1:Hu/4ERvoWaN6aiFs4h4/yvVB
|
||||
yunion.io/x/ovsdb v0.0.0-20230306173834-f164f413a900/go.mod h1:0vLkNEhlmA64HViPBAnSTUMrx5QP1CLsxXmxDKQ80tc=
|
||||
yunion.io/x/pkg v0.0.0-20190620104149-945c25821dbf/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
|
||||
yunion.io/x/pkg v0.0.0-20190628082551-f4033ba2ea30/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
|
||||
yunion.io/x/pkg v1.10.4-0.20260422030155-01b100134978 h1:+BS9DValwi0xHa92xrB+RR6Cxp7bDbXVysa/KiC18rY=
|
||||
yunion.io/x/pkg v1.10.4-0.20260422030155-01b100134978/go.mod h1:0Bwxqd9MA3ACi119/l02FprY/o9gHahmYC2bsSbnVpM=
|
||||
yunion.io/x/pkg v1.10.4-0.20260720021919-5e34f63c5815 h1:EkdBOh0J26NeFlgEh0Shrg+S+4Y86Msx6TQtaayVoZo=
|
||||
yunion.io/x/pkg v1.10.4-0.20260720021919-5e34f63c5815/go.mod h1:0Bwxqd9MA3ACi119/l02FprY/o9gHahmYC2bsSbnVpM=
|
||||
yunion.io/x/s3cli v0.0.0-20241221171442-1c11599d28e1 h1:1KJ3YYinydPHpDEQRXdr/T8SYcKZ5Er+m489H+PnaQ4=
|
||||
yunion.io/x/s3cli v0.0.0-20241221171442-1c11599d28e1/go.mod h1:0iFKpOs1y4lbCxeOmq3Xx/0AcQoewVPwj62eRluioEo=
|
||||
yunion.io/x/sqlchemy v1.1.3-0.20251231025938-b0a38f6e9fab h1:5m/bSzW3uTAk83rp9eethbYsxJFYInFVeU1RDkteW4E=
|
||||
|
||||
@@ -1609,6 +1609,7 @@ var messageKeyToIndex = map[string]int{
|
||||
"loadbalancer is already associated with eip": 780,
|
||||
"loadbalancer is locked, cannot delete": 1366,
|
||||
"local host storage is not empty": 1177,
|
||||
"local_path SKU with HAMi requires per-GPU VRAM: set devices[].memory_mb on the LLM SKU": 2131,
|
||||
"locally stored disks do not support detaching": 394,
|
||||
"locally stored system disks do not support changing configuration": 391,
|
||||
"login_account exceeds 32 characters": 820,
|
||||
@@ -2172,7 +2173,7 @@ var messageKeyToIndex = map[string]int{
|
||||
"zone_ids must be in the same cloudregion": 1582,
|
||||
}
|
||||
|
||||
var en_USIndex = []uint32{ // 2132 elements
|
||||
var en_USIndex = []uint32{ // 2133 elements
|
||||
// Entry 0 - 1F
|
||||
0x00000000, 0x0000001b, 0x00000034, 0x00000049,
|
||||
0x00000067, 0x0000007a, 0x00000099, 0x000000d4,
|
||||
@@ -2773,9 +2774,10 @@ var en_USIndex = []uint32{ // 2132 elements
|
||||
0x00011c60, 0x00011c78, 0x00011c8e, 0x00011c99,
|
||||
0x00011ca7, 0x00011cba, 0x00011ccf, 0x00011ce4,
|
||||
0x00011cf3, 0x00011d12, 0x00011d2c, 0x00011d41,
|
||||
} // Size: 8552 bytes
|
||||
0x00011d98,
|
||||
} // Size: 8532 bytes
|
||||
|
||||
const en_USData string = "" + // Size: 73025 bytes
|
||||
const en_USData string = "" + // Size: 73112 bytes
|
||||
"\x02not found tenantId in body\x02failed to change project\x02get passwo" +
|
||||
"rd in body\x02username or password is empty\x02missing credential\x02inc" +
|
||||
"orrect username or password\x02The user has been locked, please contact " +
|
||||
@@ -3881,9 +3883,10 @@ const en_USData string = "" + // Size: 73025 bytes
|
||||
"vn controller configuration\x02unmarshal input fail %s\x02invalid charac" +
|
||||
"ters %s\x02bad config\x02network error\x02ssh protocol error\x02invalid " +
|
||||
"vpc provider\x02invalid ovn database\x02user not found\x02check name dup" +
|
||||
"lication fail %s\x02getReferenceCount fail %s\x02policy is referenced"
|
||||
"lication fail %s\x02getReferenceCount fail %s\x02policy is referenced" +
|
||||
"\x02local_path SKU with HAMi requires per-GPU VRAM: set devices[].memory_mb on the LLM SKU"
|
||||
|
||||
var zh_CNIndex = []uint32{ // 2132 elements
|
||||
var zh_CNIndex = []uint32{ // 2133 elements
|
||||
// Entry 0 - 1F
|
||||
0x00000000, 0x0000001b, 0x0000002e, 0x00000049,
|
||||
0x00000062, 0x00000075, 0x00000094, 0x000000bf,
|
||||
@@ -4484,9 +4487,10 @@ var zh_CNIndex = []uint32{ // 2132 elements
|
||||
0x00010c9c, 0x00010cb1, 0x00010cc7, 0x00010cd7,
|
||||
0x00010ce4, 0x00010cf4, 0x00010d0a, 0x00010d20,
|
||||
0x00010d30, 0x00010d4e, 0x00010d71, 0x00010d8a,
|
||||
} // Size: 8552 bytes
|
||||
0x00010e12,
|
||||
} // Size: 8532 bytes
|
||||
|
||||
const zh_CNData string = "" + // Size: 69002 bytes
|
||||
const zh_CNData string = "" + // Size: 69138 bytes
|
||||
"\x02获取tenantId参数失败\x02切换项目失败\x02获取password参数失败\x02用户名或密码为空\x02缺少认证信息\x02" +
|
||||
"用户名或者密码不正确\x02用户已经被锁定,请联系管理员\x02用户已经被禁用,请联系管理员\x02用户已经被禁用或处于不可用状态\x02无" +
|
||||
"效的认证信息\x02获取请求的JSON内容失败:%v\x02此用户禁止从Web控制台登录\x02请求内容为空\x02缺少id参数\x02fe" +
|
||||
@@ -5070,6 +5074,7 @@ const zh_CNData string = "" + // Size: 69002 bytes
|
||||
"告警条件类型: %s\x02解析URL %q出错:%v\x02无效的IP\x02停止dap搜索\x02ovn controller\x02o" +
|
||||
"vn controller配置\x02解析input失败 %s\x02不支持的字符 %s\x02无效的配置\x02网络错误\x02ssh协议错误" +
|
||||
"\x02无效的vpc provider\x02无效的ovn database\x02找不到用户\x02检查重复名称失败:%s\x02getRef" +
|
||||
"erenceCount调用出错:%s\x02权限定义正被使用"
|
||||
"erenceCount调用出错:%s\x02权限定义正被使用" +
|
||||
"\x02主机本地模型(local_path)推理模板使用 HAMi 时必须设置每卡显存:请到推理模板中填写 GPU 显存(MiB)"
|
||||
|
||||
// Total table size 159131 bytes (155KiB); checksum: A1EC1008
|
||||
|
||||
@@ -9951,6 +9951,11 @@
|
||||
"message": "auto_gpu_memory_utilization requires GPU devices: configure GPU on the LLM SKU",
|
||||
"translation": "开启 auto_gpu_memory_utilization 需要先在推理模板中配置 GPU 设备"
|
||||
},
|
||||
{
|
||||
"id": "local_path SKU with HAMi requires per-GPU VRAM: set devices[].memory_mb on the LLM SKU",
|
||||
"message": "local_path SKU with HAMi requires per-GPU VRAM: set devices[].memory_mb on the LLM SKU",
|
||||
"translation": "主机本地模型(local_path)推理模板使用 HAMi 时必须设置每卡显存:请到推理模板中填写 GPU 显存(MiB)"
|
||||
},
|
||||
{
|
||||
"id": "LLM is not running",
|
||||
"message": "LLM is not running",
|
||||
|
||||
@@ -33,6 +33,13 @@ type aiKeyHealthState struct {
|
||||
cooldownUntil time.Time
|
||||
}
|
||||
|
||||
// aiKeyHealthSnapshot is a read-only view for error messages (not exported as API).
|
||||
type aiKeyHealthSnapshot struct {
|
||||
score int
|
||||
inCooldown bool
|
||||
remainingSec int
|
||||
}
|
||||
|
||||
var (
|
||||
aiKeyHealthMu sync.RWMutex
|
||||
aiKeyHealth = map[string]*aiKeyHealthState{}
|
||||
@@ -57,6 +64,27 @@ func getAiKeyHealth(keyId string) *aiKeyHealthState {
|
||||
return st
|
||||
}
|
||||
|
||||
// aiKeyHealthInfo returns a read-only snapshot for diagnostics / error text.
|
||||
func aiKeyHealthInfo(keyId string) aiKeyHealthSnapshot {
|
||||
if keyId == "" {
|
||||
return aiKeyHealthSnapshot{score: aiKeyHealthMaxScore}
|
||||
}
|
||||
st := getAiKeyHealth(keyId)
|
||||
now := time.Now()
|
||||
aiKeyHealthMu.RLock()
|
||||
defer aiKeyHealthMu.RUnlock()
|
||||
info := aiKeyHealthSnapshot{score: st.score}
|
||||
if !st.cooldownUntil.IsZero() && now.Before(st.cooldownUntil) {
|
||||
info.inCooldown = true
|
||||
sec := int(st.cooldownUntil.Sub(now).Seconds())
|
||||
if sec < 1 {
|
||||
sec = 1
|
||||
}
|
||||
info.remainingSec = sec
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// dynamicAiKeyWeightMultiplier returns 0-100 applied to configured ai_key.weight (100 = full weight).
|
||||
func dynamicAiKeyWeightMultiplier(keyId string) int {
|
||||
if keyId == "" {
|
||||
@@ -75,7 +103,12 @@ func dynamicAiKeyWeightMultiplier(keyId string) int {
|
||||
st.score = aiKeyHealthMaxScore / 2
|
||||
}
|
||||
}
|
||||
// score<=0 without an active cooldown would permanently exclude the key;
|
||||
// start a cooldown so it can recover via the path above after the period.
|
||||
if st.score <= 0 {
|
||||
if st.cooldownUntil.IsZero() {
|
||||
st.cooldownUntil = now.Add(aiKeyHealthCooldownPeriod)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if st.score > aiKeyHealthMaxScore {
|
||||
@@ -113,7 +146,8 @@ func RecordAiKeyFailure(keyId string, statusCode int) {
|
||||
if st.score < 0 {
|
||||
st.score = 0
|
||||
}
|
||||
if st.consecutiveFails >= aiKeyHealthCooldownAfter {
|
||||
// Enter cooldown on streak or when score is exhausted (avoids permanent blacklist).
|
||||
if st.consecutiveFails >= aiKeyHealthCooldownAfter || st.score <= 0 {
|
||||
st.cooldownUntil = time.Now().Add(aiKeyHealthCooldownPeriod)
|
||||
st.score = 0
|
||||
}
|
||||
|
||||
215
pkg/aiproxy/models/ai_key_health_test.go
Normal file
215
pkg/aiproxy/models/ai_key_health_test.go
Normal file
@@ -0,0 +1,215 @@
|
||||
// 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 (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/aiproxy"
|
||||
)
|
||||
|
||||
func resetAiKeyHealthForTest() {
|
||||
aiKeyHealthMu.Lock()
|
||||
aiKeyHealth = map[string]*aiKeyHealthState{}
|
||||
aiKeyHealthMu.Unlock()
|
||||
}
|
||||
|
||||
func TestRecordAiKeyFailure_ConsecutiveTriggersCooldown(t *testing.T) {
|
||||
resetAiKeyHealthForTest()
|
||||
const id = "key-consec"
|
||||
for i := 0; i < aiKeyHealthCooldownAfter; i++ {
|
||||
RecordAiKeyFailure(id, 429)
|
||||
}
|
||||
if mul := dynamicAiKeyWeightMultiplier(id); mul != 0 {
|
||||
t.Fatalf("expected multiplier 0 during cooldown, got %d", mul)
|
||||
}
|
||||
info := aiKeyHealthInfo(id)
|
||||
if !info.inCooldown {
|
||||
t.Fatal("expected inCooldown after consecutive failures")
|
||||
}
|
||||
if info.score != 0 {
|
||||
t.Fatalf("expected score 0, got %d", info.score)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordAiKeyFailure_IntermittentScoreExhaustionEntersCooldown(t *testing.T) {
|
||||
resetAiKeyHealthForTest()
|
||||
const id = "key-intermittent"
|
||||
// fail -25 / success +10 with consecutiveFails reset: score can hit 0 without 3 consecutive fails.
|
||||
for {
|
||||
info := aiKeyHealthInfo(id)
|
||||
if info.score <= 0 || info.inCooldown {
|
||||
break
|
||||
}
|
||||
RecordAiKeyFailure(id, 500)
|
||||
info = aiKeyHealthInfo(id)
|
||||
if info.score <= 0 || info.inCooldown {
|
||||
break
|
||||
}
|
||||
RecordAiKeySuccess(id)
|
||||
if dynamicAiKeyWeightMultiplier(id) <= 0 {
|
||||
t.Fatal("unexpected zero multiplier after success")
|
||||
}
|
||||
}
|
||||
info := aiKeyHealthInfo(id)
|
||||
if !info.inCooldown {
|
||||
t.Fatalf("expected cooldown after score exhaustion, got score=%d inCooldown=%v", info.score, info.inCooldown)
|
||||
}
|
||||
if mul := dynamicAiKeyWeightMultiplier(id); mul != 0 {
|
||||
t.Fatalf("expected multiplier 0 during cooldown, got %d", mul)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDynamicAiKeyWeightMultiplier_RecoversAfterCooldown(t *testing.T) {
|
||||
resetAiKeyHealthForTest()
|
||||
const id = "key-recover"
|
||||
RecordAiKeyFailure(id, 429)
|
||||
RecordAiKeyFailure(id, 429)
|
||||
RecordAiKeyFailure(id, 429)
|
||||
st := getAiKeyHealth(id)
|
||||
aiKeyHealthMu.Lock()
|
||||
st.cooldownUntil = time.Now().Add(-time.Second)
|
||||
aiKeyHealthMu.Unlock()
|
||||
|
||||
mul := dynamicAiKeyWeightMultiplier(id)
|
||||
if mul < aiKeyHealthMaxScore/2 {
|
||||
t.Fatalf("expected recovered multiplier >= %d, got %d", aiKeyHealthMaxScore/2, mul)
|
||||
}
|
||||
info := aiKeyHealthInfo(id)
|
||||
if info.inCooldown {
|
||||
t.Fatal("expected cooldown cleared after expiry")
|
||||
}
|
||||
if info.score < aiKeyHealthMaxScore/2 {
|
||||
t.Fatalf("expected score >= %d, got %d", aiKeyHealthMaxScore/2, info.score)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDynamicAiKeyWeightMultiplier_StuckScoreStartsCooldown(t *testing.T) {
|
||||
resetAiKeyHealthForTest()
|
||||
const id = "key-stuck"
|
||||
st := getAiKeyHealth(id)
|
||||
aiKeyHealthMu.Lock()
|
||||
st.score = 0
|
||||
st.cooldownUntil = time.Time{}
|
||||
aiKeyHealthMu.Unlock()
|
||||
|
||||
if mul := dynamicAiKeyWeightMultiplier(id); mul != 0 {
|
||||
t.Fatalf("expected 0, got %d", mul)
|
||||
}
|
||||
info := aiKeyHealthInfo(id)
|
||||
if !info.inCooldown {
|
||||
t.Fatal("expected fallback cooldown for stuck score=0")
|
||||
}
|
||||
|
||||
aiKeyHealthMu.Lock()
|
||||
st.cooldownUntil = time.Now().Add(-time.Second)
|
||||
aiKeyHealthMu.Unlock()
|
||||
mul := dynamicAiKeyWeightMultiplier(id)
|
||||
if mul < aiKeyHealthMaxScore/2 {
|
||||
t.Fatalf("expected recovery after fallback cooldown, got %d", mul)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecordAiKeySuccess_ClearsCooldown(t *testing.T) {
|
||||
resetAiKeyHealthForTest()
|
||||
const id = "key-success"
|
||||
RecordAiKeyFailure(id, 401)
|
||||
RecordAiKeyFailure(id, 401)
|
||||
RecordAiKeyFailure(id, 401)
|
||||
RecordAiKeySuccess(id)
|
||||
info := aiKeyHealthInfo(id)
|
||||
if info.inCooldown {
|
||||
t.Fatal("success should clear cooldown")
|
||||
}
|
||||
if mul := dynamicAiKeyWeightMultiplier(id); mul <= 0 {
|
||||
t.Fatalf("expected positive multiplier after success, got %d", mul)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAiKeySkipReason(t *testing.T) {
|
||||
resetAiKeyHealthForTest()
|
||||
|
||||
t.Run("already tried", func(t *testing.T) {
|
||||
k := &SAiKey{Secret: "sk-test"}
|
||||
k.Id = "id-tried"
|
||||
k.Name = "tried-key"
|
||||
reason := aiKeySkipReason(k, "deepseek-v4-pro", map[string]bool{"id-tried": true})
|
||||
if !strings.Contains(reason, "already tried") {
|
||||
t.Fatalf("got %q", reason)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("cooldown", func(t *testing.T) {
|
||||
k := &SAiKey{Secret: "sk-test", Weight: 1}
|
||||
k.Id = "id-cd"
|
||||
k.Name = "cd-key"
|
||||
RecordAiKeyFailure(k.Id, 429)
|
||||
RecordAiKeyFailure(k.Id, 429)
|
||||
RecordAiKeyFailure(k.Id, 429)
|
||||
reason := aiKeySkipReason(k, "deepseek-v4-pro", nil)
|
||||
if !strings.Contains(reason, "cooldown") || !strings.Contains(reason, "remaining") {
|
||||
t.Fatalf("got %q", reason)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("routing", func(t *testing.T) {
|
||||
resetAiKeyHealthForTest()
|
||||
k := &SAiKey{
|
||||
Secret: "sk-test",
|
||||
Weight: 1,
|
||||
Routing: &api.SAiKeyRouting{
|
||||
AllowedModelKeys: []string{"other-model"},
|
||||
},
|
||||
}
|
||||
k.Id = "id-route"
|
||||
k.Name = "route-key"
|
||||
reason := aiKeySkipReason(k, "deepseek-v4-pro", nil)
|
||||
if !strings.Contains(reason, "model not allowed by routing") {
|
||||
t.Fatalf("got %q", reason)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty secret", func(t *testing.T) {
|
||||
k := &SAiKey{Secret: " "}
|
||||
k.Name = "empty-key"
|
||||
reason := aiKeySkipReason(k, "m", nil)
|
||||
if !strings.Contains(reason, "empty secret") {
|
||||
t.Fatalf("got %q", reason)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("usable", func(t *testing.T) {
|
||||
resetAiKeyHealthForTest()
|
||||
k := &SAiKey{Secret: "sk-ok", Weight: 1}
|
||||
k.Id = "id-ok"
|
||||
k.Name = "ok-key"
|
||||
if reason := aiKeySkipReason(k, "deepseek-v4-pro", nil); reason != "" {
|
||||
t.Fatalf("expected empty reason, got %q", reason)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFormatAiKeySkipReasons_Truncates(t *testing.T) {
|
||||
reasons := make([]string, maxAiKeySkipReasonsInError+3)
|
||||
for i := range reasons {
|
||||
reasons[i] = "r"
|
||||
}
|
||||
out := formatAiKeySkipReasons(reasons)
|
||||
if !strings.Contains(out, "and 3 more") {
|
||||
t.Fatalf("got %q", out)
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ package models
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
@@ -25,6 +26,8 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
)
|
||||
|
||||
const maxAiKeySkipReasonsInError = 8
|
||||
|
||||
func effectiveAiKeyRoutingWeight(r *api.SAiKeyRouting) int {
|
||||
if r == nil || r.Weight <= 0 {
|
||||
return 0
|
||||
@@ -84,6 +87,58 @@ func aiKeyRoutingAcceptsModel(r *api.SAiKeyRouting, reqModel string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func aiKeyLabel(k *SAiKey) string {
|
||||
if k == nil {
|
||||
return "?"
|
||||
}
|
||||
if n := strings.TrimSpace(k.Name); n != "" {
|
||||
return n
|
||||
}
|
||||
if id := strings.TrimSpace(k.Id); id != "" {
|
||||
return id
|
||||
}
|
||||
return "?"
|
||||
}
|
||||
|
||||
// aiKeySkipReason returns why an ai_key cannot be used for modelKey, or "" if usable.
|
||||
func aiKeySkipReason(k *SAiKey, modelKey string, exclude map[string]bool) string {
|
||||
if k == nil {
|
||||
return "?: nil ai_key"
|
||||
}
|
||||
label := aiKeyLabel(k)
|
||||
if strings.TrimSpace(k.Secret) == "" {
|
||||
return label + ": empty secret"
|
||||
}
|
||||
if exclude != nil && exclude[k.Id] {
|
||||
return label + ": already tried"
|
||||
}
|
||||
if baseAiKeyWeight(k) <= 0 {
|
||||
return label + ": weight=0"
|
||||
}
|
||||
if effectiveAiKeyWeight(k) <= 0 {
|
||||
info := aiKeyHealthInfo(k.Id)
|
||||
if info.inCooldown {
|
||||
return fmt.Sprintf("%s: cooldown %ds remaining (health_score=%d)", label, info.remainingSec, info.score)
|
||||
}
|
||||
return fmt.Sprintf("%s: health_score=%d", label, info.score)
|
||||
}
|
||||
if !aiKeyRoutingAcceptsModel(k.Routing, modelKey) {
|
||||
return label + ": model not allowed by routing (allowed_model_keys/blocked_model_keys)"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func formatAiKeySkipReasons(reasons []string) string {
|
||||
if len(reasons) == 0 {
|
||||
return ""
|
||||
}
|
||||
if len(reasons) <= maxAiKeySkipReasonsInError {
|
||||
return strings.Join(reasons, "; ")
|
||||
}
|
||||
shown := strings.Join(reasons[:maxAiKeySkipReasonsInError], "; ")
|
||||
return fmt.Sprintf("%s; and %d more", shown, len(reasons)-maxAiKeySkipReasonsInError)
|
||||
}
|
||||
|
||||
func pickWeightedAiKey(candidates []*SAiKey) *SAiKey {
|
||||
if len(candidates) == 0 {
|
||||
return nil
|
||||
@@ -145,21 +200,18 @@ func resolveUpstreamAPIKeyExcluding(prov *SAiProvider, modelKey string, exclude
|
||||
|
||||
candidates := make([]*SAiKey, 0, len(keys))
|
||||
hasSecretKey := false
|
||||
skipReasons := make([]string, 0, len(keys))
|
||||
for i := range keys {
|
||||
k := &keys[i]
|
||||
if strings.TrimSpace(k.Secret) == "" {
|
||||
continue
|
||||
}
|
||||
hasSecretKey = true
|
||||
if exclude != nil && exclude[k.Id] {
|
||||
if reason := aiKeySkipReason(k, modelKey, exclude); reason != "" {
|
||||
skipReasons = append(skipReasons, reason)
|
||||
continue
|
||||
}
|
||||
if effectiveAiKeyWeight(k) <= 0 {
|
||||
continue
|
||||
}
|
||||
if aiKeyRoutingAcceptsModel(k.Routing, modelKey) {
|
||||
candidates = append(candidates, k)
|
||||
}
|
||||
candidates = append(candidates, k)
|
||||
}
|
||||
if len(candidates) > 0 {
|
||||
chosen := pickWeightedAiKey(candidates)
|
||||
@@ -173,7 +225,11 @@ func resolveUpstreamAPIKeyExcluding(prov *SAiProvider, modelKey string, exclude
|
||||
}, nil
|
||||
}
|
||||
if hasSecretKey {
|
||||
return nil, errors.Wrapf(httperrors.ErrInvalidStatus, "no available ai_key for catalog model %q (check weight, cooldown, allowed_model_keys)", modelKey)
|
||||
detail := formatAiKeySkipReasons(skipReasons)
|
||||
if detail != "" {
|
||||
return nil, errors.Wrapf(httperrors.ErrInvalidStatus, "no available ai_key for catalog model %q: %s", modelKey, detail)
|
||||
}
|
||||
return nil, errors.Wrapf(httperrors.ErrInvalidStatus, "no available ai_key for catalog model %q", modelKey)
|
||||
}
|
||||
return nil, errors.Wrap(httperrors.ErrInvalidStatus, "add an enabled ai_key with secret for this provider")
|
||||
}
|
||||
|
||||
@@ -225,14 +225,90 @@ func TestResponsesStreamConverterText(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
foundCompleted := false
|
||||
foundOutputItemDone := false
|
||||
for _, e := range end {
|
||||
if e.Event == "response.completed" {
|
||||
foundCompleted = true
|
||||
}
|
||||
if e.Event == "response.output_item.done" {
|
||||
foundOutputItemDone = true
|
||||
}
|
||||
}
|
||||
if !foundCompleted {
|
||||
t.Fatalf("events = %+v", end)
|
||||
}
|
||||
if !foundOutputItemDone {
|
||||
t.Fatalf("expected response.output_item.done in %v", responsesEventNames(end))
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponsesStreamConverterReasoningAndToolDoneEvents(t *testing.T) {
|
||||
conv := NewResponsesStreamConverter("deepseek-v4-flash", nil)
|
||||
chunks := [][]byte{
|
||||
[]byte(`{"id":"chatcmpl-1","model":"deepseek-v4-flash","choices":[{"delta":{"role":"assistant","reasoning_content":"think"}}]}`),
|
||||
[]byte(`{"id":"chatcmpl-1","model":"deepseek-v4-flash","choices":[{"delta":{"tool_calls":[{"index":0,"id":"call_1","type":"function","function":{"name":"exec_command","arguments":"{\"cmd\":\"ls\"}"}}]}}]}`),
|
||||
[]byte(`{"id":"chatcmpl-1","model":"deepseek-v4-flash","choices":[{"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":10,"completion_tokens":5,"total_tokens":15}}`),
|
||||
}
|
||||
var events []ResponsesStreamEvent
|
||||
for _, chunk := range chunks {
|
||||
evs, err := conv.Feed(chunk, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
events = append(events, evs...)
|
||||
}
|
||||
end, err := conv.Feed(nil, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
events = append(events, end...)
|
||||
|
||||
counts := map[string]int{}
|
||||
for _, e := range events {
|
||||
counts[e.Event]++
|
||||
}
|
||||
if counts["response.reasoning_summary_part.done"] != 1 {
|
||||
t.Fatalf("reasoning_summary_part.done = %d, events=%v", counts["response.reasoning_summary_part.done"], responsesEventNames(events))
|
||||
}
|
||||
if counts["response.function_call_arguments.done"] != 1 {
|
||||
t.Fatalf("function_call_arguments.done = %d, events=%v", counts["response.function_call_arguments.done"], responsesEventNames(events))
|
||||
}
|
||||
if counts["response.output_item.done"] < 2 {
|
||||
t.Fatalf("output_item.done = %d, want at least 2, events=%v", counts["response.output_item.done"], responsesEventNames(events))
|
||||
}
|
||||
if counts["response.completed"] != 1 {
|
||||
t.Fatalf("completed = %d, events=%v", counts["response.completed"], responsesEventNames(events))
|
||||
}
|
||||
|
||||
// output_item.done for tools must appear before response.completed
|
||||
lastToolDone := -1
|
||||
completedAt := -1
|
||||
for i, e := range events {
|
||||
if e.Event == "response.output_item.done" {
|
||||
var payload map[string]interface{}
|
||||
if err := json.Unmarshal(e.Data, &payload); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
item, _ := payload["item"].(map[string]interface{})
|
||||
if item["type"] == "function_call" {
|
||||
lastToolDone = i
|
||||
}
|
||||
}
|
||||
if e.Event == "response.completed" {
|
||||
completedAt = i
|
||||
}
|
||||
}
|
||||
if lastToolDone < 0 || completedAt < 0 || lastToolDone >= completedAt {
|
||||
t.Fatalf("tool output_item.done must precede completed: toolDone=%d completed=%d", lastToolDone, completedAt)
|
||||
}
|
||||
}
|
||||
|
||||
func responsesEventNames(events []ResponsesStreamEvent) []string {
|
||||
out := make([]string, len(events))
|
||||
for i, e := range events {
|
||||
out[i] = e.Event
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func TestNonStreamChatCompletionToStreamPayloads(t *testing.T) {
|
||||
|
||||
@@ -17,6 +17,7 @@ package openai
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -32,23 +33,27 @@ type ResponsesStreamEvent struct {
|
||||
|
||||
// ResponsesStreamConverter converts OpenAI chat.completion.chunk SSE to Responses SSE events.
|
||||
type ResponsesStreamConverter struct {
|
||||
requestModel string
|
||||
toolMap CodexToolMap
|
||||
created bool
|
||||
completed bool
|
||||
seq int64
|
||||
responseID string
|
||||
model string
|
||||
outputIndex int
|
||||
textItemID string
|
||||
reasonItemID string
|
||||
textStarted bool
|
||||
reasonStarted bool
|
||||
textBuf strings.Builder
|
||||
reasonBuf strings.Builder
|
||||
inputTokens int
|
||||
outputTokens int
|
||||
activeTools map[int]*responsesStreamToolState
|
||||
requestModel string
|
||||
toolMap CodexToolMap
|
||||
created bool
|
||||
completed bool
|
||||
seq int64
|
||||
responseID string
|
||||
model string
|
||||
outputIndex int
|
||||
textItemID string
|
||||
reasonItemID string
|
||||
textOutputIndex int
|
||||
reasonOutputIndex int
|
||||
textStarted bool
|
||||
reasonStarted bool
|
||||
textDone bool
|
||||
reasonDone bool
|
||||
textBuf strings.Builder
|
||||
reasonBuf strings.Builder
|
||||
inputTokens int
|
||||
outputTokens int
|
||||
activeTools map[int]*responsesStreamToolState
|
||||
}
|
||||
|
||||
type responsesStreamToolState struct {
|
||||
@@ -59,6 +64,7 @@ type responsesStreamToolState struct {
|
||||
namespace string
|
||||
argsBuf strings.Builder
|
||||
added bool
|
||||
finalized bool
|
||||
}
|
||||
|
||||
// NewResponsesStreamConverter creates stream state for one Responses response.
|
||||
@@ -190,6 +196,7 @@ func (s *ResponsesStreamConverter) appendText(text string) ([]ResponsesStreamEve
|
||||
var out []ResponsesStreamEvent
|
||||
if !s.textStarted {
|
||||
s.textStarted = true
|
||||
s.textOutputIndex = s.outputIndex
|
||||
s.textItemID = fmt.Sprintf("msg_%d", s.outputIndex)
|
||||
added, err := s.outputItemAdded("message", s.textItemID, map[string]interface{}{
|
||||
"type": "message",
|
||||
@@ -222,6 +229,7 @@ func (s *ResponsesStreamConverter) appendReasoning(text string) ([]ResponsesStre
|
||||
var out []ResponsesStreamEvent
|
||||
if !s.reasonStarted {
|
||||
s.reasonStarted = true
|
||||
s.reasonOutputIndex = s.outputIndex
|
||||
s.reasonItemID = fmt.Sprintf("rs_%d", s.outputIndex)
|
||||
added, err := s.outputItemAdded("reasoning", s.reasonItemID, map[string]interface{}{
|
||||
"type": "reasoning",
|
||||
@@ -362,11 +370,182 @@ func (s *ResponsesStreamConverter) textDelta(text string) (ResponsesStreamEvent,
|
||||
return ResponsesStreamEvent{Event: "response.output_text.delta", Data: b}, nil
|
||||
}
|
||||
|
||||
func (s *ResponsesStreamConverter) emitCompleted() ([]ResponsesStreamEvent, error) {
|
||||
if s.completed {
|
||||
func (s *ResponsesStreamConverter) emitFinalizeEvents() ([]ResponsesStreamEvent, error) {
|
||||
var out []ResponsesStreamEvent
|
||||
if s.reasonStarted && !s.reasonDone {
|
||||
events, err := s.emitReasoningDone()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, events...)
|
||||
}
|
||||
if s.textStarted && !s.textDone {
|
||||
events, err := s.emitTextDone()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, events...)
|
||||
}
|
||||
toolIndexes := make([]int, 0, len(s.activeTools))
|
||||
for idx := range s.activeTools {
|
||||
toolIndexes = append(toolIndexes, idx)
|
||||
}
|
||||
sort.Ints(toolIndexes)
|
||||
for _, idx := range toolIndexes {
|
||||
st := s.activeTools[idx]
|
||||
if st == nil || !st.added || st.finalized {
|
||||
continue
|
||||
}
|
||||
events, err := s.emitToolDone(st)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, events...)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *ResponsesStreamConverter) emitReasoningDone() ([]ResponsesStreamEvent, error) {
|
||||
if !s.reasonStarted || s.reasonDone {
|
||||
return nil, nil
|
||||
}
|
||||
s.completed = true
|
||||
s.reasonDone = true
|
||||
var out []ResponsesStreamEvent
|
||||
partDone := map[string]interface{}{
|
||||
"type": "response.reasoning_summary_part.done",
|
||||
"sequence_number": s.nextSeq(),
|
||||
"item_id": s.reasonItemID,
|
||||
"output_index": s.reasonOutputIndex,
|
||||
"summary_index": 0,
|
||||
}
|
||||
b, err := json.Marshal(partDone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, ResponsesStreamEvent{Event: "response.reasoning_summary_part.done", Data: b})
|
||||
|
||||
item := map[string]interface{}{
|
||||
"type": "reasoning",
|
||||
"id": s.reasonItemID,
|
||||
"status": "completed",
|
||||
"summary": []map[string]interface{}{
|
||||
{"type": "text", "text": s.reasonBuf.String()},
|
||||
},
|
||||
}
|
||||
itemDone, err := s.outputItemDoneAt(s.reasonOutputIndex, item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, itemDone)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *ResponsesStreamConverter) emitTextDone() ([]ResponsesStreamEvent, error) {
|
||||
if !s.textStarted || s.textDone {
|
||||
return nil, nil
|
||||
}
|
||||
s.textDone = true
|
||||
text := s.textBuf.String()
|
||||
var out []ResponsesStreamEvent
|
||||
textDone := map[string]interface{}{
|
||||
"type": "response.output_text.done",
|
||||
"sequence_number": s.nextSeq(),
|
||||
"item_id": s.textItemID,
|
||||
"output_index": s.textOutputIndex,
|
||||
"content_index": 0,
|
||||
"text": text,
|
||||
}
|
||||
b, err := json.Marshal(textDone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, ResponsesStreamEvent{Event: "response.output_text.done", Data: b})
|
||||
|
||||
partDone := map[string]interface{}{
|
||||
"type": "response.content_part.done",
|
||||
"sequence_number": s.nextSeq(),
|
||||
"item_id": s.textItemID,
|
||||
"output_index": s.textOutputIndex,
|
||||
"content_index": 0,
|
||||
"part": map[string]interface{}{"type": "output_text", "text": text},
|
||||
}
|
||||
b, err = json.Marshal(partDone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, ResponsesStreamEvent{Event: "response.content_part.done", Data: b})
|
||||
|
||||
item := map[string]interface{}{
|
||||
"type": "message",
|
||||
"id": s.textItemID,
|
||||
"status": "completed",
|
||||
"role": "assistant",
|
||||
"content": []map[string]interface{}{
|
||||
{"type": "output_text", "text": text},
|
||||
},
|
||||
}
|
||||
itemDone, err := s.outputItemDoneAt(s.textOutputIndex, item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, itemDone)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *ResponsesStreamConverter) emitToolDone(st *responsesStreamToolState) ([]ResponsesStreamEvent, error) {
|
||||
if st == nil || !st.added || st.finalized {
|
||||
return nil, nil
|
||||
}
|
||||
st.finalized = true
|
||||
args := st.argsBuf.String()
|
||||
var out []ResponsesStreamEvent
|
||||
argsDone := map[string]interface{}{
|
||||
"type": "response.function_call_arguments.done",
|
||||
"sequence_number": s.nextSeq(),
|
||||
"item_id": st.itemID,
|
||||
"output_index": st.outputIndex,
|
||||
"arguments": args,
|
||||
}
|
||||
b, err := json.Marshal(argsDone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, ResponsesStreamEvent{Event: "response.function_call_arguments.done", Data: b})
|
||||
|
||||
item := map[string]interface{}{
|
||||
"type": "function_call",
|
||||
"id": st.itemID,
|
||||
"status": "completed",
|
||||
"call_id": st.callID,
|
||||
"name": st.name,
|
||||
"arguments": args,
|
||||
}
|
||||
if st.namespace != "" {
|
||||
item["namespace"] = st.namespace
|
||||
}
|
||||
itemDone, err := s.outputItemDoneAt(st.outputIndex, item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, itemDone)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *ResponsesStreamConverter) outputItemDoneAt(outputIndex int, item map[string]interface{}) (ResponsesStreamEvent, error) {
|
||||
data := map[string]interface{}{
|
||||
"type": "response.output_item.done",
|
||||
"sequence_number": s.nextSeq(),
|
||||
"output_index": outputIndex,
|
||||
"item": item,
|
||||
}
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return ResponsesStreamEvent{}, err
|
||||
}
|
||||
return ResponsesStreamEvent{Event: "response.output_item.done", Data: b}, nil
|
||||
}
|
||||
|
||||
func (s *ResponsesStreamConverter) buildCompletedOutput() []interface{} {
|
||||
output := make([]interface{}, 0)
|
||||
if s.reasonBuf.Len() > 0 {
|
||||
output = append(output, map[string]interface{}{
|
||||
@@ -385,7 +564,13 @@ func (s *ResponsesStreamConverter) emitCompleted() ([]ResponsesStreamEvent, erro
|
||||
"content": []map[string]interface{}{{"type": "text", "text": s.textBuf.String()}},
|
||||
})
|
||||
}
|
||||
for _, st := range s.activeTools {
|
||||
toolIndexes := make([]int, 0, len(s.activeTools))
|
||||
for idx := range s.activeTools {
|
||||
toolIndexes = append(toolIndexes, idx)
|
||||
}
|
||||
sort.Ints(toolIndexes)
|
||||
for _, idx := range toolIndexes {
|
||||
st := s.activeTools[idx]
|
||||
if st == nil || !st.added {
|
||||
continue
|
||||
}
|
||||
@@ -401,14 +586,26 @@ func (s *ResponsesStreamConverter) emitCompleted() ([]ResponsesStreamEvent, erro
|
||||
}
|
||||
output = append(output, item)
|
||||
}
|
||||
status := "completed"
|
||||
return output
|
||||
}
|
||||
|
||||
func (s *ResponsesStreamConverter) emitCompleted() ([]ResponsesStreamEvent, error) {
|
||||
if s.completed {
|
||||
return nil, nil
|
||||
}
|
||||
finalizeEvents, err := s.emitFinalizeEvents()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.completed = true
|
||||
output := s.buildCompletedOutput()
|
||||
data := map[string]interface{}{
|
||||
"type": "response.completed",
|
||||
"sequence_number": s.nextSeq(),
|
||||
"response": map[string]interface{}{
|
||||
"id": s.responseID,
|
||||
"object": "response",
|
||||
"status": status,
|
||||
"status": "completed",
|
||||
"model": s.model,
|
||||
"output": output,
|
||||
"output_text": s.textBuf.String(),
|
||||
@@ -423,7 +620,8 @@ func (s *ResponsesStreamConverter) emitCompleted() ([]ResponsesStreamEvent, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []ResponsesStreamEvent{{Event: "response.completed", Data: b}}, nil
|
||||
out := append(finalizeEvents, ResponsesStreamEvent{Event: "response.completed", Data: b})
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ToProviderChunks maps internal events to providerapi chunks.
|
||||
|
||||
@@ -65,6 +65,10 @@ func (h *SBackendServiceProxyHandler) requestManipulator(ctx context.Context, r
|
||||
if slashPos <= 0 {
|
||||
return r, httperrors.NewBadRequestError("invalid request URL %s", r.URL.Path)
|
||||
}
|
||||
serviceName := path[:slashPos]
|
||||
// Tell upstream (e.g. mcp-server SSE) the external path prefix so endpoint
|
||||
// events point clients back through the gateway: /api/s/<service>/message
|
||||
r.Header.Set("X-Forwarded-Prefix", "/api/s/"+serviceName)
|
||||
path = path[slashPos:]
|
||||
if strings.HasPrefix(path, "/r/") {
|
||||
path = path[len("/r/"):]
|
||||
|
||||
@@ -86,6 +86,8 @@ func mcpAgentChatStreamHandler(ctx context.Context, w http.ResponseWriter, r *ht
|
||||
// Prepare request to backend
|
||||
headers := http.Header{}
|
||||
headers.Set("Content-Type", "application/json")
|
||||
headers.Set("Accept", "text/event-stream")
|
||||
headers.Set("Accept-Encoding", "identity")
|
||||
|
||||
// Forward the request body to the backend
|
||||
var bodyReader io.Reader
|
||||
@@ -125,7 +127,9 @@ func mcpAgentChatStreamHandler(ctx context.Context, w http.ResponseWriter, r *ht
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Connection", "keep-alive")
|
||||
// For now just standard SSE headers.
|
||||
w.Header().Set("X-Accel-Buffering", "no")
|
||||
// 禁止中间层 gzip 缓冲整段 SSE
|
||||
w.Header().Set("Content-Encoding", "identity")
|
||||
|
||||
if f, ok := w.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
@@ -160,6 +164,8 @@ func mcpAgentDefaultChatStreamHandler(ctx context.Context, w http.ResponseWriter
|
||||
|
||||
headers := http.Header{}
|
||||
headers.Set("Content-Type", "application/json")
|
||||
headers.Set("Accept", "text/event-stream")
|
||||
headers.Set("Accept-Encoding", "identity")
|
||||
|
||||
var bodyReader io.Reader
|
||||
if r.Body != nil {
|
||||
@@ -194,6 +200,8 @@ func mcpAgentDefaultChatStreamHandler(ctx context.Context, w http.ResponseWriter
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Connection", "keep-alive")
|
||||
w.Header().Set("X-Accel-Buffering", "no")
|
||||
w.Header().Set("Content-Encoding", "identity")
|
||||
if f, ok := w.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ type IModelSet interface {
|
||||
NewModel() db.IModel
|
||||
AddModel(db.IModel)
|
||||
Copy() IModelSet
|
||||
IncludeDetails() bool
|
||||
}
|
||||
|
||||
type IDBModelSet interface {
|
||||
@@ -107,7 +108,7 @@ func syncModelSets(mssOld IModelSets, s *mcclient.ClientSession, opt *Options) (
|
||||
ModelSet: msNew,
|
||||
BatchListSize: opt.ListBatchSize,
|
||||
|
||||
IncludeDetails: opt.IncludeDetails,
|
||||
IncludeDetails: msNew.IncludeDetails(),
|
||||
IncludeEmulated: includeEmulated,
|
||||
InCludeOtherCloudEnv: opt.IncludeOtherCloudEnv,
|
||||
})
|
||||
@@ -134,7 +135,7 @@ func SyncDBModelSets(mssOld IModelSets, s *mcclient.ClientSession, opt *Options)
|
||||
ModelSet: msNew,
|
||||
BatchListSize: opt.ListBatchSize,
|
||||
|
||||
IncludeDetails: opt.IncludeDetails,
|
||||
IncludeDetails: msNew.IncludeDetails(),
|
||||
IncludeEmulated: includeEmulated,
|
||||
InCludeOtherCloudEnv: opt.IncludeOtherCloudEnv,
|
||||
}
|
||||
|
||||
@@ -122,6 +122,8 @@ type CloudgroupAttachPolicyInput struct {
|
||||
|
||||
// 权限Id
|
||||
CloudpolicyId string `json:"cloudpolicy_id"`
|
||||
// 权限Id列表
|
||||
CloudpolicyIds []string `json:"cloudpolicy_ids"`
|
||||
}
|
||||
|
||||
type CloudgroupSetUsersInput struct {
|
||||
@@ -140,6 +142,8 @@ type CloudgroupDetachPolicyInput struct {
|
||||
|
||||
// 权限Id
|
||||
CloudpolicyId string `json:"cloudpolicy_id"`
|
||||
// 权限Id列表
|
||||
CloudpolicyIds []string `json:"cloudpolicy_ids"`
|
||||
}
|
||||
|
||||
type CloudgroupSyncstatusInput struct {
|
||||
|
||||
@@ -315,17 +315,22 @@ type IsolatedDeviceConfig struct {
|
||||
Id string `json:"id"`
|
||||
DevType string `json:"dev_type"`
|
||||
Model string `json:"model"`
|
||||
SharingMode string `json:"sharing_mode"`
|
||||
Vendor string `json:"vendor"`
|
||||
NetworkIndex *int `json:"network_index"`
|
||||
WireId string `json:"wire_id"`
|
||||
DiskIndex *int8 `json:"disk_index"`
|
||||
DevicePath string `json:"device_path"`
|
||||
GpuType string `json:"gpu_type"`
|
||||
// MemoryMb is the minimum on-device memory in MiB required from the
|
||||
// candidate isolated_device (e.g. NVIDIA GPU VRAM). 0 means no constraint.
|
||||
// The scheduler excludes devices whose memory_size > 0 and is below this
|
||||
// threshold; devices with memory_size == 0 are treated as unknown and
|
||||
// allowed through to avoid penalising hosts that haven't reported yet.
|
||||
MemoryMb int `json:"memory_mb,omitempty"`
|
||||
// Memory request for Devices allocate by Memory size
|
||||
MemoryRequest int `json:"memory_request,omitempty"`
|
||||
SmUtilLimit int `json:"sm_util_limit,omitempty"`
|
||||
}
|
||||
|
||||
type BaremetalDiskConfig struct {
|
||||
|
||||
@@ -40,7 +40,9 @@ const (
|
||||
CONTAINER_DEV_NVIDIA_GPU = "NVIDIA_GPU"
|
||||
CONTAINER_DEV_NVIDIA_MPS = "NVIDIA_MPS"
|
||||
CONTAINER_DEV_NVIDIA_GPU_SHARE = "NVIDIA_GPU_SHARE"
|
||||
CONTAINER_DEV_NVIDIA_HAMI = "NVIDIA_HAMI"
|
||||
CONTAINER_DEV_ASCEND_NPU = "ASCEND_NPU"
|
||||
CONTAINER_DEV_ASCEND_NPU_HAMI = "ASCEND_NPU_HAMI"
|
||||
CONTAINER_DEV_VASTAITECH_GPU = "VASTAITECH_GPU"
|
||||
)
|
||||
|
||||
@@ -49,14 +51,16 @@ var (
|
||||
CONTAINER_DEV_CPH_AMD_GPU,
|
||||
CONTAINER_DEV_NVIDIA_GPU,
|
||||
CONTAINER_DEV_NVIDIA_MPS,
|
||||
CONTAINER_DEV_NVIDIA_HAMI,
|
||||
CONTAINER_DEV_NVIDIA_GPU_SHARE,
|
||||
CONTAINER_DEV_VASTAITECH_GPU,
|
||||
}
|
||||
)
|
||||
|
||||
var NVIDIA_GPU_TYPES = []string{
|
||||
var CONTAINER_NVIDIA_GPU_TYPES = []string{
|
||||
CONTAINER_DEV_NVIDIA_GPU,
|
||||
CONTAINER_DEV_NVIDIA_MPS,
|
||||
CONTAINER_DEV_NVIDIA_HAMI,
|
||||
CONTAINER_DEV_NVIDIA_GPU_SHARE,
|
||||
}
|
||||
|
||||
@@ -198,10 +202,11 @@ type ContainerHostDevice struct {
|
||||
}
|
||||
|
||||
type ContainerIsolatedDevice struct {
|
||||
Index *int `json:"index"`
|
||||
Id string `json:"id"`
|
||||
OnlyEnv []*apis.ContainerIsolatedDeviceOnlyEnv `json:"only_env"`
|
||||
CDI *apis.ContainerIsolatedDeviceCDI `json:"cdi"`
|
||||
Index *int `json:"index"`
|
||||
Id string `json:"id"`
|
||||
GuestIsolatedDeviceIndex int `json:"guest_isolated_device_index"`
|
||||
OnlyEnv []*apis.ContainerIsolatedDeviceOnlyEnv `json:"only_env"`
|
||||
CDI *apis.ContainerIsolatedDeviceCDI `json:"cdi"`
|
||||
}
|
||||
|
||||
type ContainerDevice struct {
|
||||
|
||||
@@ -97,3 +97,15 @@ const (
|
||||
DISK_DRIVER_SATA = "sata"
|
||||
DISK_DRIVER_VFIO = "vfio-pci"
|
||||
)
|
||||
|
||||
const (
|
||||
DISK_CACHE_MODE_WRITETHROGH = "writethrough"
|
||||
DISK_CACHE_MODE_NONE = "none"
|
||||
DISK_CACHE_MODE_WRITEBACK = "writeback"
|
||||
DISK_CACHE_MODE_DIRECTSYNC = "directsync"
|
||||
)
|
||||
|
||||
const (
|
||||
DISK_AIO_MODE_NATIVE = "native"
|
||||
DISK_AIO_MOD_THREADS = "threads"
|
||||
)
|
||||
|
||||
@@ -319,6 +319,7 @@ var HYPERVISORS = []string{
|
||||
HYPERVISOR_SANGFOR,
|
||||
HYPERVISOR_ZETTAKIT,
|
||||
HYPERVISOR_UIS,
|
||||
HYPERVISOR_CAS,
|
||||
HYPERVISOR_CNWARE,
|
||||
HYPERVISOR_ROCKBASE,
|
||||
}
|
||||
|
||||
@@ -56,11 +56,11 @@ type GuestdiskListInput struct {
|
||||
type GuestdiskUpdateInput struct {
|
||||
GuestJointBaseUpdateInput
|
||||
|
||||
Driver string `json:"driver"`
|
||||
|
||||
CacheMode string `json:"cache_mode"`
|
||||
|
||||
AioMode string `json:"aio_mode"`
|
||||
//Driver string `json:"driver"`
|
||||
//
|
||||
//CacheMode string `json:"cache_mode"`
|
||||
//
|
||||
//AioMode string `json:"aio_mode"`
|
||||
|
||||
Iops *int `json:"iops"`
|
||||
|
||||
|
||||
@@ -131,6 +131,8 @@ type ServerListInput struct {
|
||||
|
||||
SnapshotpolicyId string `json:"snapshotpolicy_id"`
|
||||
|
||||
IsolatedDeviceId string `json:"isolated_device_id"`
|
||||
|
||||
// 是否调度到宿主机上
|
||||
WithHost *bool `json:"with_host"`
|
||||
|
||||
@@ -1063,8 +1065,10 @@ type ServerChangeDiskStorageInput struct {
|
||||
}
|
||||
|
||||
type ServerChangeDiskDriverInput struct {
|
||||
DiskId string `json:"disk_id"`
|
||||
Driver string `json:"driver"`
|
||||
DiskId string `json:"disk_id"`
|
||||
Driver string `json:"driver"`
|
||||
CacheMode string `json:"cache_mode"`
|
||||
AioMode string `json:"aio_mode"`
|
||||
}
|
||||
|
||||
type ServerChangeDiskStorageInternalInput struct {
|
||||
@@ -1569,8 +1573,25 @@ func (conf ServerChangeConfigSettings) AddedDisk() int {
|
||||
}
|
||||
|
||||
type ServerReleasedIsolatedDevice struct {
|
||||
DevType string `json:"dev_type"`
|
||||
Model string `json:"model"`
|
||||
DevType string `json:"dev_type"`
|
||||
Model string `json:"model"`
|
||||
GpuType string `json:"gpu_type"`
|
||||
SharingMode string `json:"sharing_mode"`
|
||||
MemoryRequest int `json:"memory_request"`
|
||||
}
|
||||
|
||||
type ServerAttachIsolatedDeviceBase struct {
|
||||
AutoStart bool `json:"auto_start"`
|
||||
GpuType string `json:"gpu_type"`
|
||||
MemoryRequest *int `json:"memory_request"`
|
||||
SharingMode string `json:"sharing_mode"`
|
||||
Count *int `json:"count"`
|
||||
}
|
||||
|
||||
type ServerAttachIsolatedDeviceInput struct {
|
||||
ServerAttachIsolatedDeviceBase
|
||||
Device string `json:"device"`
|
||||
Model string `json:"model"`
|
||||
}
|
||||
|
||||
type ServerChangeBillingTypeInput struct {
|
||||
|
||||
@@ -501,6 +501,8 @@ type HostIpmiAttributes struct {
|
||||
IpmiPresent *bool `json:"ipmi_present"`
|
||||
// lan channel
|
||||
IpmiLanChannel *uint8 `json:"ipmi_lan_channel"`
|
||||
// RMCP+ cipher suite for ipmitool -C
|
||||
IpmiCipherSuite *int `json:"ipmi_cipher_suite"`
|
||||
// verified
|
||||
IpmiVerified *bool `json:"ipmi_verified"`
|
||||
// Redfish API support
|
||||
@@ -775,7 +777,7 @@ type HostUploadGuestsStatusInput struct {
|
||||
}
|
||||
|
||||
type HostIsolatedDeviceNumaStatsInput struct {
|
||||
DevType string
|
||||
Model string
|
||||
}
|
||||
|
||||
type GuestUploadContainerStatusResponse struct {
|
||||
|
||||
119
pkg/apis/compute/ipset.go
Normal file
119
pkg/apis/compute/ipset.go
Normal file
@@ -0,0 +1,119 @@
|
||||
// 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 compute
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/pkg/util/regutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
)
|
||||
|
||||
type TIpSetType string
|
||||
|
||||
const (
|
||||
IpSetTypeIpv4CidrList TIpSetType = "ipv4_cidr_list"
|
||||
IpSetTypeIpv6CidrList TIpSetType = "ipv6_cidr_list"
|
||||
)
|
||||
|
||||
func (t TIpSetType) IsValid() bool {
|
||||
switch t {
|
||||
case IpSetTypeIpv4CidrList, IpSetTypeIpv6CidrList:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type IpSetCreateInput struct {
|
||||
apis.SharableVirtualResourceCreateInput
|
||||
|
||||
// IP集合类型
|
||||
// enum: ["ipv4_cidr_list", "ipv6_cidr_list"]
|
||||
// required: true
|
||||
IpSetType TIpSetType `json:"ip_set_type"`
|
||||
|
||||
// IP/CIDR 列表,逗号分隔
|
||||
// example: 192.168.1.0/24,10.0.0.1
|
||||
// required: true
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type IpSetUpdateInput struct {
|
||||
apis.SharableVirtualResourceBaseUpdateInput
|
||||
|
||||
// IP/CIDR 列表,逗号分隔
|
||||
// example: 192.168.1.0/24,10.0.0.1
|
||||
Data *string `json:"data"`
|
||||
}
|
||||
|
||||
type IpSetListInput struct {
|
||||
apis.SharableVirtualResourceListInput
|
||||
|
||||
// 按 IP 集合类型过滤
|
||||
// enum: ["ipv4_cidr_list", "ipv6_cidr_list"]
|
||||
IpSetType []TIpSetType `json:"ip_set_type"`
|
||||
|
||||
// 根据 IP/CIDR 模糊匹配
|
||||
Ip string `json:"ip"`
|
||||
}
|
||||
|
||||
type IpSetDetails struct {
|
||||
apis.SharableVirtualResourceDetails
|
||||
|
||||
// 关联安全组数量
|
||||
SecurityGroupCount int `json:"security_group_count"`
|
||||
}
|
||||
|
||||
func ValidateIpSetData(ipSetType TIpSetType, data string) error {
|
||||
if !ipSetType.IsValid() {
|
||||
return httperrors.NewInputParameterError("invalid ip_set_type %s", ipSetType)
|
||||
}
|
||||
data = strings.TrimSpace(data)
|
||||
if len(data) == 0 {
|
||||
return httperrors.NewMissingParameterError("data")
|
||||
}
|
||||
parts := strings.Split(data, ",")
|
||||
for i := range parts {
|
||||
cidr := strings.TrimSpace(parts[i])
|
||||
if len(cidr) == 0 {
|
||||
return httperrors.NewInputParameterError("empty cidr in data")
|
||||
}
|
||||
switch ipSetType {
|
||||
case IpSetTypeIpv4CidrList:
|
||||
if !regutils.MatchCIDR(cidr) && !regutils.MatchIP4Addr(cidr) {
|
||||
return httperrors.NewInputParameterError("invalid ipv4 cidr or address %s", cidr)
|
||||
}
|
||||
case IpSetTypeIpv6CidrList:
|
||||
if !regutils.MatchCIDR6(cidr) && !regutils.MatchIP6Addr(cidr) {
|
||||
return httperrors.NewInputParameterError("invalid ipv6 cidr or address %s", cidr)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (input *IpSetCreateInput) Validate() error {
|
||||
return ValidateIpSetData(input.IpSetType, input.Data)
|
||||
}
|
||||
|
||||
func (input *IpSetUpdateInput) Validate(currentType TIpSetType) error {
|
||||
if input.Data != nil {
|
||||
return ValidateIpSetData(currentType, *input.Data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -34,10 +34,13 @@ type IsolateDeviceDetails struct {
|
||||
|
||||
SIsolatedDevice
|
||||
|
||||
MemoryAllocated int
|
||||
AllocatedCount int
|
||||
|
||||
// 云主机名称
|
||||
Guest string `json:"guest"`
|
||||
Guest []string `json:"guest"`
|
||||
// 云主机状态
|
||||
GuestStatus string `json:"guest_status"`
|
||||
GuestStatus []string `json:"guest_status"`
|
||||
}
|
||||
|
||||
type IsolatedDeviceListInput struct {
|
||||
@@ -95,6 +98,9 @@ type IsolatedDeviceCreateInput struct {
|
||||
// example: GPU
|
||||
DevType string `json:"dev_type"`
|
||||
|
||||
// DEVICE sharing mode
|
||||
SharingMode string `json:"sharing_mode"`
|
||||
|
||||
// 设备型号
|
||||
// # Specific device name read from lspci command, e.g. `Tesla K40m` ...
|
||||
Model string `json:"model"`
|
||||
@@ -137,9 +143,28 @@ type IsolatedDeviceUpdateInput struct {
|
||||
DeviceMinor int `json:"device_minor"`
|
||||
}
|
||||
|
||||
type SDelIsolatedDeviceInput struct {
|
||||
Device string
|
||||
Index int
|
||||
}
|
||||
|
||||
type SAddIsolatedDeviceInput struct {
|
||||
Device string
|
||||
GpuType string
|
||||
MemoryRequest *int
|
||||
}
|
||||
|
||||
type SetIsolatedDeviceInput struct {
|
||||
AddDevices []SAddIsolatedDeviceInput
|
||||
DelDevices []SDelIsolatedDeviceInput
|
||||
AutoStart bool
|
||||
}
|
||||
|
||||
type IsolatedDeviceJsonDesc struct {
|
||||
Id string `json:"id"`
|
||||
DevType string `json:"dev_type"`
|
||||
GpuType string `json:"gpu_type"`
|
||||
SharingMode string `json:"sharing_mode"`
|
||||
Model string `json:"model"`
|
||||
Addr string `json:"addr"`
|
||||
VendorDeviceId string `json:"vendor_device_id"`
|
||||
@@ -152,6 +177,8 @@ type IsolatedDeviceJsonDesc struct {
|
||||
MemorySize int `json:"memory_size"`
|
||||
MdevId string `json:"mdev_id"`
|
||||
NumaNode int8 `json:"numa_node"`
|
||||
MemoryLimit int `json:"memory_limit"`
|
||||
SmUtilLimit int `json:"sm_util_limit"`
|
||||
}
|
||||
|
||||
type IsolatedDeviceModelCreateInput struct {
|
||||
@@ -368,3 +395,22 @@ type HostIsolatedDeviceModelDetails struct {
|
||||
HotPluggable bool
|
||||
DisableAutoDetect bool
|
||||
}
|
||||
|
||||
type IsolatedDeviceFilterListInput struct {
|
||||
IsolateDeviceIds []string `json:"isolate_device_ids"`
|
||||
}
|
||||
|
||||
type GuestIsolatedDeviceListInput struct {
|
||||
GuestJointsListInput
|
||||
|
||||
IsolatedDeviceListInput
|
||||
IsolatedDeviceFilterListInput
|
||||
}
|
||||
|
||||
type GuestIsolatedDeviceDetails struct {
|
||||
GuestJointResourceDetails
|
||||
SGuestIsolatedDevice
|
||||
SIsolatedDevice
|
||||
HostResourceInfo
|
||||
apis.SharableResourceBaseInfo
|
||||
}
|
||||
|
||||
@@ -15,25 +15,83 @@
|
||||
package compute
|
||||
|
||||
const (
|
||||
DIRECT_PCI_TYPE = "PCI"
|
||||
GPU_HPC_TYPE = "GPU-HPC" // # for compute
|
||||
GPU_VGA_TYPE = "GPU-VGA" // # for display
|
||||
SRIOV_VGPU_TYPE = "SRIOV-VGPU"
|
||||
LEGACY_VGPU_TYPE = "LEGACY-VGPU"
|
||||
USB_TYPE = "USB"
|
||||
NIC_TYPE = "NIC" // nic sriov
|
||||
NVME_PT_TYPE = "NVME-PT" // nvme passthrough
|
||||
|
||||
NVIDIA_VENDOR_ID = "10de"
|
||||
AMD_VENDOR_ID = "1002"
|
||||
DIRECT_PCI_TYPE = "PCI"
|
||||
GPU_TYPE = "GPU"
|
||||
USB_TYPE = "USB"
|
||||
NIC_TYPE = "NIC" // nic sriov
|
||||
NVME_PT_TYPE = "NVME-PT" // nvme passthrough
|
||||
NETINT_TYPE = "NETINT"
|
||||
NPU_TYPE = "NPU"
|
||||
BINDER_TYPE = "BINDER"
|
||||
)
|
||||
|
||||
var VALID_GPU_TYPES = []string{GPU_HPC_TYPE, GPU_VGA_TYPE}
|
||||
var VALID_ATTACH_TYPES = []string{GPU_HPC_TYPE, GPU_VGA_TYPE, USB_TYPE, SRIOV_VGPU_TYPE, LEGACY_VGPU_TYPE}
|
||||
var VALID_TYPES = []string{DIRECT_PCI_TYPE, GPU_TYPE, USB_TYPE, NIC_TYPE, NVME_PT_TYPE, NETINT_TYPE, NPU_TYPE, BINDER_TYPE}
|
||||
|
||||
const (
|
||||
DEVICE_SHARING_MODE_EXCLUSIVE = "EXCLUSIVE"
|
||||
DEVICE_SHARING_MODE_SRIOV = "SRIOV"
|
||||
DEVICE_SHARING_MODE_MPS = "MPS"
|
||||
DEVICE_SHARING_MODE_HAMI = "HAMI"
|
||||
DEVICE_SHARING_MODE_UNLIMITED = "UNLIMITED"
|
||||
DEVICE_SHARING_MODE_MDEV = "MDEV"
|
||||
)
|
||||
|
||||
var VIRTUAL_SHARING_MODES = []string{DEVICE_SHARING_MODE_HAMI, DEVICE_SHARING_MODE_UNLIMITED, DEVICE_SHARING_MODE_MPS}
|
||||
var VAILD_SHARING_MODES = []string{DEVICE_SHARING_MODE_EXCLUSIVE, DEVICE_SHARING_MODE_SRIOV, DEVICE_SHARING_MODE_MDEV, DEVICE_SHARING_MODE_HAMI, DEVICE_SHARING_MODE_UNLIMITED, DEVICE_SHARING_MODE_MPS}
|
||||
|
||||
const (
|
||||
GPU_HPC = "HPC"
|
||||
GPU_VGA = "VGA"
|
||||
)
|
||||
|
||||
const (
|
||||
NVIDIA_VENDOR_ID = "10de"
|
||||
AMD_VENDOR_ID = "1002"
|
||||
VASTAITECH_VENDOR_ID = "1ec6"
|
||||
)
|
||||
|
||||
var ID_VENDOR_MAP = map[string]string{
|
||||
NVIDIA_VENDOR_ID: "NVIDIA",
|
||||
AMD_VENDOR_ID: "AMD",
|
||||
VASTAITECH_VENDOR_ID: "VASTAITECH",
|
||||
}
|
||||
|
||||
var VENDOR_ID_MAP = map[string]string{
|
||||
"NVIDIA": NVIDIA_VENDOR_ID,
|
||||
"AMD": AMD_VENDOR_ID,
|
||||
}
|
||||
|
||||
const (
|
||||
ISOLATED_DEVICE_MODEL_METADATA_BANDWIDTH = "bandwidth"
|
||||
ISOLATED_DEVICE_MODEL_METADATA_MEMORY_MB = "memory_mb"
|
||||
ISOLATED_DEVICE_MODEL_METADATA_TFLOPS = "tflops"
|
||||
)
|
||||
|
||||
////////////// deprecated
|
||||
|
||||
const (
|
||||
GPU_HPC_TYPE = "GPU-HPC" // # for compute
|
||||
GPU_VGA_TYPE = "GPU-VGA" // # for display
|
||||
SRIOV_VGPU_TYPE = "SRIOV-VGPU" //done
|
||||
LEGACY_VGPU_TYPE = "LEGACY-VGPU" //done
|
||||
)
|
||||
|
||||
var VALID_GPU_TYPES = []string{GPU_HPC_TYPE, GPU_VGA_TYPE} //done
|
||||
var VALID_ATTACH_TYPES = []string{GPU_HPC_TYPE, GPU_VGA_TYPE, USB_TYPE, SRIOV_VGPU_TYPE, LEGACY_VGPU_TYPE} //done
|
||||
|
||||
var GPU_TYPES = []string{
|
||||
GPU_HPC_TYPE, GPU_VGA_TYPE, SRIOV_VGPU_TYPE, LEGACY_VGPU_TYPE,
|
||||
CONTAINER_DEV_CPH_AMD_GPU, CONTAINER_DEV_NVIDIA_GPU, CONTAINER_DEV_NVIDIA_MPS, CONTAINER_DEV_NVIDIA_GPU_SHARE,
|
||||
CONTAINER_DEV_NVIDIA_HAMI, CONTAINER_DEV_VASTAITECH_GPU,
|
||||
}
|
||||
|
||||
var NETINT_TYPES = []string{
|
||||
CONTAINER_DEV_NETINT_CA_QUADRA, CONTAINER_DEV_NETINT_CA_ASIC,
|
||||
}
|
||||
|
||||
var VALID_CONTAINER_DEVICE_TYPES = []string{
|
||||
CONTAINER_DEV_CPH_AMD_GPU, CONTAINER_DEV_CPH_AOSP_BINDER, CONTAINER_DEV_NETINT_CA_QUADRA,
|
||||
CONTAINER_DEV_NETINT_CA_ASIC, CONTAINER_DEV_NVIDIA_GPU, CONTAINER_DEV_NVIDIA_MPS, CONTAINER_DEV_NVIDIA_GPU_SHARE,
|
||||
CONTAINER_DEV_NETINT_CA_ASIC, CONTAINER_DEV_NVIDIA_GPU, CONTAINER_DEV_NVIDIA_MPS, CONTAINER_DEV_NVIDIA_GPU_SHARE, CONTAINER_DEV_NVIDIA_HAMI,
|
||||
CONTAINER_DEV_ASCEND_NPU, CONTAINER_DEV_VASTAITECH_GPU,
|
||||
}
|
||||
|
||||
@@ -49,27 +107,12 @@ var VITRUAL_DEVICE_TYPES = []string{
|
||||
CONTAINER_DEV_NETINT_CA_QUADRA,
|
||||
CONTAINER_DEV_NETINT_CA_ASIC,
|
||||
CONTAINER_DEV_NVIDIA_MPS,
|
||||
CONTAINER_DEV_NVIDIA_HAMI,
|
||||
CONTAINER_DEV_ASCEND_NPU,
|
||||
CONTAINER_DEV_NVIDIA_GPU_SHARE,
|
||||
CONTAINER_DEV_VASTAITECH_GPU,
|
||||
}
|
||||
|
||||
var ID_VENDOR_MAP = map[string]string{
|
||||
NVIDIA_VENDOR_ID: "NVIDIA",
|
||||
AMD_VENDOR_ID: "AMD",
|
||||
}
|
||||
|
||||
var VENDOR_ID_MAP = map[string]string{
|
||||
"NVIDIA": NVIDIA_VENDOR_ID,
|
||||
"AMD": AMD_VENDOR_ID,
|
||||
}
|
||||
|
||||
const (
|
||||
ISOLATED_DEVICE_MODEL_METADATA_BANDWIDTH = "bandwidth"
|
||||
ISOLATED_DEVICE_MODEL_METADATA_MEMORY_MB = "memory_mb"
|
||||
ISOLATED_DEVICE_MODEL_METADATA_TFLOPS = "tflops"
|
||||
)
|
||||
|
||||
func init() {
|
||||
VALID_PASSTHROUGH_TYPES = append(VALID_PASSTHROUGH_TYPES, VALID_CONTAINER_DEVICE_TYPES...)
|
||||
VALID_ATTACH_TYPES = append(VALID_ATTACH_TYPES, VALID_CONTAINER_DEVICE_TYPES...)
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package compute
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/pkg/errors"
|
||||
@@ -71,6 +70,11 @@ type SSecgroupRuleResource struct {
|
||||
// example: 192.168.222.121
|
||||
CIDR string `json:"cidr"`
|
||||
|
||||
// 目标类型
|
||||
// enum: ["cidr", "ip_set", "security_group"]
|
||||
// required: true
|
||||
TargetType TSecgroupTargetType `json:"target_type"`
|
||||
|
||||
// 行为
|
||||
// deny: 拒绝
|
||||
// allow: 允许
|
||||
@@ -135,6 +139,9 @@ type SSecgroupRuleUpdateInput struct {
|
||||
// requried: false
|
||||
// example: test to create rule
|
||||
Description string `json:"description"`
|
||||
|
||||
// swagger:ignore
|
||||
TargetType TSecgroupTargetType `json:"target_type"`
|
||||
}
|
||||
|
||||
func IsValidSecgroupRuleCIDR(cidr string) bool {
|
||||
@@ -177,13 +184,28 @@ func (input *SSecgroupRuleResource) Check() error {
|
||||
}
|
||||
}
|
||||
|
||||
if len(input.CIDR) > 0 {
|
||||
if !IsValidSecgroupRuleCIDR(input.CIDR) {
|
||||
return fmt.Errorf("invalid cidr: %s", input.CIDR)
|
||||
switch input.TargetType {
|
||||
case SecurityGroupRuleTargetTypeCidr:
|
||||
if len(input.CIDR) > 0 {
|
||||
if !IsValidSecgroupRuleCIDR(input.CIDR) {
|
||||
return errors.Wrapf(errors.ErrInvalidFormat, "invalid cidr: %s", input.CIDR)
|
||||
}
|
||||
} else {
|
||||
// empty CIDR means both IPv4 and IPv6
|
||||
// input.CIDR = "0.0.0.0/0"
|
||||
}
|
||||
} else {
|
||||
// empty CIDR means both IPv4 and IPv6
|
||||
// input.CIDR = "0.0.0.0/0"
|
||||
case SecurityGroupRuleTargetTypeIpSet:
|
||||
if len(input.CIDR) > 0 {
|
||||
|
||||
} else {
|
||||
return errors.Wrap(errors.ErrEmpty, "empty ip set id")
|
||||
}
|
||||
case SecurityGroupRuleTargetTypeIpSetGroup:
|
||||
fallthrough
|
||||
case SecurityGroupRuleTargetTypeSecurityGroup:
|
||||
fallthrough
|
||||
default:
|
||||
return errors.Wrapf(errors.ErrNotSupported, "unsupported target type %s", input.TargetType)
|
||||
}
|
||||
|
||||
return rule.ValidateRule()
|
||||
@@ -239,6 +261,9 @@ type SecgroupListInput struct {
|
||||
LoadbalancerId string `json:"loadbalancer_id"`
|
||||
RegionalFilterListInput
|
||||
ManagedResourceListInput
|
||||
|
||||
// 指定过滤规则中含有指定ip集的安全组
|
||||
IpSetId []string `json:"ip_set_id"`
|
||||
}
|
||||
|
||||
type SecurityGroupRuleListInput struct {
|
||||
@@ -258,6 +283,8 @@ type SecurityGroupRuleListInput struct {
|
||||
Ports string `json:"ports"`
|
||||
// 根据ip模糊匹配安全组规则
|
||||
Ip string `json:"ip"`
|
||||
// 根据target_type字段过滤安全组规则
|
||||
TargetType []string `json:"target_type"`
|
||||
}
|
||||
|
||||
type SecgroupResourceInput struct {
|
||||
|
||||
@@ -16,6 +16,15 @@ package compute
|
||||
|
||||
import "yunion.io/x/onecloud/pkg/apis"
|
||||
|
||||
type TSecgroupTargetType string
|
||||
|
||||
const (
|
||||
SecurityGroupRuleTargetTypeIpSet TSecgroupTargetType = "ip_set"
|
||||
SecurityGroupRuleTargetTypeIpSetGroup TSecgroupTargetType = "ip_set_group"
|
||||
SecurityGroupRuleTargetTypeSecurityGroup TSecgroupTargetType = "security_group"
|
||||
SecurityGroupRuleTargetTypeCidr TSecgroupTargetType = "cidr"
|
||||
)
|
||||
|
||||
type SecgroupRuleDetails struct {
|
||||
apis.ResourceBaseDetails
|
||||
apis.ProjectizedResourceInfo
|
||||
@@ -23,4 +32,7 @@ type SecgroupRuleDetails struct {
|
||||
SecurityGroupResourceInfo
|
||||
|
||||
ProjectId string `json:"tenant_id"`
|
||||
|
||||
TargetIpSet string `json:"target_ip_set"`
|
||||
TargetSecurityGroup string `json:"target_security_group"`
|
||||
}
|
||||
|
||||
@@ -23,6 +23,13 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/apis/cloudprovider"
|
||||
)
|
||||
|
||||
// IsolatedDeviceAllocateStat is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.IsolatedDeviceAllocateStat.
|
||||
type IsolatedDeviceAllocateStat struct {
|
||||
SIsolatedDevice
|
||||
GuestCount int `json:"guest_count"`
|
||||
MemoryAllocated int `json:"memory_allocated"`
|
||||
}
|
||||
|
||||
// SAccessGroup is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SAccessGroup.
|
||||
type SAccessGroup struct {
|
||||
apis.SStatusInfrasResourceBase
|
||||
@@ -167,6 +174,7 @@ type SBucket struct {
|
||||
SizeBytesLimit int64 `json:"size_bytes_limit"`
|
||||
ObjectCntLimit int `json:"object_cnt_limit"`
|
||||
AccessUrls jsonutils.JSONObject `json:"access_urls"`
|
||||
EnablePerfMon bool `json:"enable_perf_mon"`
|
||||
}
|
||||
|
||||
// SCDNDomain is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SCDNDomain.
|
||||
@@ -650,6 +658,8 @@ type SDnsRecord struct {
|
||||
DnsValue string `json:"dns_value"`
|
||||
TTL int64 `json:"ttl"`
|
||||
MxPriority int64 `json:"mx_priority"`
|
||||
// cloudflare 特有
|
||||
Proxied *bool `json:"proxied,omitempty"`
|
||||
// 解析线路类型
|
||||
PolicyType string `json:"policy_type"`
|
||||
// 解析线路
|
||||
@@ -940,6 +950,10 @@ type SElasticip struct {
|
||||
BgpType string `json:"bgp_type"`
|
||||
// 是否跟随主机删除而自动释放
|
||||
AutoDellocate *bool `json:"auto_dellocate,omitempty"`
|
||||
// 下行带宽限制,单位mbps
|
||||
RxBwLimit int `json:"rx_bw_limit"`
|
||||
// 上行带宽限制,单位mbps
|
||||
TxBwLimit int `json:"tx_bw_limit"`
|
||||
}
|
||||
|
||||
// SExternalProject is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SExternalProject.
|
||||
@@ -1114,6 +1128,23 @@ type SGuest struct {
|
||||
ResourcePool string `json:"resource_pool"`
|
||||
}
|
||||
|
||||
// SGuestIsolatedDevice is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SGuestIsolatedDevice.
|
||||
type SGuestIsolatedDevice struct {
|
||||
SGuestJointsBase
|
||||
SIsolatedDeviceResourceBase
|
||||
// guest isolated device memory size limit
|
||||
DeviceMemorySize int `json:"device_memory_size"`
|
||||
// guest isolated device Streaming Multiprocessor Utilization limit
|
||||
SmUtilLimit int `json:"sm_util_limit"`
|
||||
// gpu device work type: HPC VGA
|
||||
GpuType string `json:"gpu_type"`
|
||||
// guest network index
|
||||
NetworkIndex int `json:"network_index"`
|
||||
// guest disk index
|
||||
DiskIndex byte `json:"disk_index"`
|
||||
Index byte `json:"index"`
|
||||
}
|
||||
|
||||
// SGuestJointsBase is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SGuestJointsBase.
|
||||
type SGuestJointsBase struct {
|
||||
apis.SVirtualJointResourceBase
|
||||
@@ -1125,11 +1156,13 @@ type SGuestNetworkTrafficLog struct {
|
||||
apis.SLogBase
|
||||
apis.SProjectizedResourceBase
|
||||
SGuestResourceBase
|
||||
SNetworkResourceBase
|
||||
// MAC地址
|
||||
Mac string `json:"mac"`
|
||||
// IPv4地址
|
||||
IpAddr string `json:"ip_addr"`
|
||||
// IPv6地址
|
||||
Ip6Addr string `json:"ip6_addr"`
|
||||
RowId int64 `json:"row_id"`
|
||||
// 下行流量,单位 bytes
|
||||
RxBytes int64 `json:"rx_bytes"`
|
||||
// 上行流量,单位 bytes
|
||||
@@ -1144,7 +1177,7 @@ type SGuestResourceBase struct {
|
||||
}
|
||||
|
||||
// SGuestScreenDump is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SGuestScreenDump.
|
||||
/* type SGuestScreenDump struct {
|
||||
type SGuestScreenDump struct {
|
||||
apis.SResourceBase
|
||||
RowId int64 `json:"row_id"`
|
||||
GuestId string `json:"guest_id"`
|
||||
@@ -1155,7 +1188,7 @@ type SGuestResourceBase struct {
|
||||
S3Endpoint string `json:"s3_endpoint"`
|
||||
S3BucketName string `json:"s3_bucket_name"`
|
||||
S3UseSsl bool `json:"s3_use_ssl"`
|
||||
} */
|
||||
}
|
||||
|
||||
// SGuestTemplate is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SGuestTemplate.
|
||||
type SGuestTemplate struct {
|
||||
@@ -1248,6 +1281,20 @@ type SGuestnetwork struct {
|
||||
PortMappings []*GuestPortMapping `json:"port_mappings"`
|
||||
SBillingTypeBase
|
||||
SBillingChargeTypeBase
|
||||
// 下行带宽限制,单位mbps
|
||||
RxBwLimit int `json:"rx_bw_limit"`
|
||||
// 上行带宽限制,单位mbps
|
||||
TxBwLimit int `json:"tx_bw_limit"`
|
||||
}
|
||||
|
||||
// SGuestnetworksecgroup is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SGuestnetworksecgroup.
|
||||
type SGuestnetworksecgroup struct {
|
||||
apis.SResourceBase
|
||||
RowId int64 `json:"row_id"`
|
||||
GuestId string `json:"guest_id"`
|
||||
SSecurityGroupResourceBase
|
||||
NetworkIndex int `json:"network_index"`
|
||||
Admin bool `json:"admin"`
|
||||
}
|
||||
|
||||
// SGuestsecgroup is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SGuestsecgroup.
|
||||
@@ -1523,7 +1570,7 @@ type SInstanceSnapshot struct {
|
||||
// 套餐名称
|
||||
InstanceType string `json:"instance_type"`
|
||||
// 主机快照磁盘容量和
|
||||
SizeMb int `json:"size_mb"`
|
||||
// SizeMb int `nullable:"false" list:"user"`
|
||||
// 镜像ID
|
||||
ImageId string `json:"image_id"`
|
||||
// 是否保存内存
|
||||
@@ -1575,15 +1622,15 @@ type SIsolatedDevice struct {
|
||||
apis.SExternalizedResourceBase
|
||||
apis.SSharableBaseResource
|
||||
SHostResourceBase
|
||||
// # PCI / GPU-HPC / GPU-VGA / USB / NIC
|
||||
// # PCI / GPU / USB / NIC ...
|
||||
// 设备类型
|
||||
DevType string `json:"dev_type"`
|
||||
// EXCLUSIVE / SRIOV / MPS / HAMI / SHARE / MIG
|
||||
SharingMode string `json:"sharing_mode"`
|
||||
// Device is hot pluggable
|
||||
HotPluggable bool `json:"hot_pluggable"`
|
||||
// # Specific device name read from lspci command, e.g. `Tesla K40m` ...
|
||||
Model string `json:"model"`
|
||||
// 云主机Id
|
||||
GuestId string `json:"guest_id"`
|
||||
// guest network index
|
||||
NetworkIndex int `json:"network_index"`
|
||||
// Nic wire id
|
||||
WireId string `json:"wire_id"`
|
||||
// Offload interface name
|
||||
@@ -1592,9 +1639,7 @@ type SIsolatedDevice struct {
|
||||
IsInfinibandNic bool `json:"is_infiniband_nic"`
|
||||
// NVME disk size
|
||||
NvmeSizeMB int `json:"nvme_size_mb"`
|
||||
// guest disk index
|
||||
DiskIndex byte `json:"disk_index"`
|
||||
// # pci address of `Bus:Device.Function` format, or usb bus address of `bus.addr`
|
||||
// # pci address of `Bus:Device.Function` format, or usb bus address of `bus:addr:port`
|
||||
Addr string `json:"addr"`
|
||||
DevicePath string `json:"device_path"`
|
||||
// GPU card path, like /dev/dri/cardX
|
||||
@@ -1640,6 +1685,11 @@ type SIsolatedDevice struct {
|
||||
PcieInfo *IsolatedDevicePCIEInfo `json:"pcie_info"`
|
||||
// device numa node
|
||||
NumaNode byte `json:"numa_node"`
|
||||
// On-device memory in MiB (NVIDIA GPU VRAM via `nvidia-smi memory.total`,
|
||||
// or per-slice quota for MPS share mode). 0 means unknown / not applicable.
|
||||
MemorySize int `json:"memory_size"`
|
||||
// some of isolated device type support virtual num, like NVIDIA_GPU_SHARE, NVIDIA_MPS
|
||||
VirtualNum int `json:"virtual_num"`
|
||||
}
|
||||
|
||||
// SIsolatedDeviceModel is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SIsolatedDeviceModel.
|
||||
@@ -1654,6 +1704,11 @@ type SIsolatedDeviceModel struct {
|
||||
DisableAutoDetect *bool `json:"disable_auto_detect,omitempty"`
|
||||
}
|
||||
|
||||
// SIsolatedDeviceResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SIsolatedDeviceResourceBase.
|
||||
type SIsolatedDeviceResourceBase struct {
|
||||
IsolatedDeviceId string `json:"isolated_device_id"`
|
||||
}
|
||||
|
||||
// SKafka is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SKafka.
|
||||
type SKafka struct {
|
||||
apis.SVirtualResourceBase
|
||||
@@ -1763,7 +1818,7 @@ type SLoadbalancer struct {
|
||||
NetworkType string `json:"network_type"`
|
||||
SLoadbalancerClusterResourceBase
|
||||
// 计费类型
|
||||
ChargeType string `json:"charge_type"`
|
||||
SBillingChargeTypeBase
|
||||
// 套餐名称
|
||||
LoadbalancerSpec string `json:"loadbalancer_spec"`
|
||||
// 默认后端服务器组Id
|
||||
@@ -2716,6 +2771,8 @@ type SSecurityGroupRule struct {
|
||||
CIDR string `json:"cidr"`
|
||||
Action string `json:"action"`
|
||||
Description string `json:"description"`
|
||||
|
||||
TargetType TSecgroupTargetType `json:"target_type"`
|
||||
}
|
||||
|
||||
// SServerSku is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SServerSku.
|
||||
@@ -2724,7 +2781,6 @@ type SServerSku struct {
|
||||
apis.SExternalizedResourceBase
|
||||
SCloudregionResourceBase
|
||||
SZoneResourceBase
|
||||
// SkuId string `width:"64" charset:"ascii" nullable:"false" list:"user" create:"admin_required"` // x2.large
|
||||
InstanceTypeFamily string `json:"instance_type_family"`
|
||||
// x2
|
||||
InstanceTypeCategory string `json:"instance_type_category"`
|
||||
@@ -2780,7 +2836,9 @@ type SSnapshot struct {
|
||||
CreatedBy string `json:"created_by"`
|
||||
Location string `json:"location"`
|
||||
// 快照大小,单位Mb
|
||||
Size int `json:"size"`
|
||||
Size int `json:"size"`
|
||||
// Virtual size, for kvm is origin disk size
|
||||
VirtualSize int `json:"virtual_size"`
|
||||
OutOfChain bool `json:"out_of_chain"`
|
||||
FakeDeleted bool `json:"fake_deleted"`
|
||||
DiskType string `json:"disk_type"`
|
||||
@@ -2799,18 +2857,15 @@ type SSnapshotPolicy struct {
|
||||
apis.SExternalizedResourceBase
|
||||
SManagedResourceBase
|
||||
SCloudregionResourceBase
|
||||
// 快照保留天数, -1: 表示永久保留
|
||||
// 快照保留天数
|
||||
RetentionDays int `json:"retention_days"`
|
||||
// 快照保留数量, 优先级高于 RetentionDays, 且仅对本地IDC资源有效
|
||||
RetentionCount int `json:"retention_count"`
|
||||
// 快照类型, 目前支持 disk, server
|
||||
// disk: 自动磁盘快照策略, 只能关联磁盘
|
||||
// server: 自动主机快照策略, 只能关联主机
|
||||
Type string `json:"type"`
|
||||
// 1~7, 1 is Monday, 7 is Sunday
|
||||
// 1~7, 1 is Monday
|
||||
RepeatWeekdays []int `json:"repeat_weekdays"`
|
||||
// 0~23, 每小时
|
||||
// 创建自动快照策略的时间必须与 RepeatWeekdays 对应的创建周期相一致
|
||||
// 0~23
|
||||
TimePoints []int `json:"time_points"`
|
||||
}
|
||||
|
||||
|
||||
@@ -93,8 +93,11 @@ type ContainerIsolatedDevice struct {
|
||||
Addr string `json:"addr"`
|
||||
Path string `json:"path"`
|
||||
DeviceType string `json:"device_type"`
|
||||
SharingMode string `json:"sharing_mode"`
|
||||
CardPath string `json:"card_path"`
|
||||
RenderPath string `json:"render_path"`
|
||||
MemoryLimit int `json:"memory_limit"`
|
||||
SmUtilLimit int `json:"sm_util_limit"`
|
||||
Index int `json:"index"`
|
||||
DeviceMinor int `json:"device_minor"`
|
||||
OnlyEnv []*apis.ContainerIsolatedDeviceOnlyEnv `json:"only_env"`
|
||||
|
||||
@@ -17,6 +17,7 @@ type InstantModelListInput struct {
|
||||
ModelTag string `json:"model_tag"`
|
||||
ModelId string `json:"model_id"`
|
||||
LlmType string `json:"llm_type"`
|
||||
Source string `json:"source"`
|
||||
Image string `json:"image"`
|
||||
|
||||
Mounts string `json:"mounts"`
|
||||
|
||||
@@ -9,7 +9,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
SERVICE_TYPE = "llm"
|
||||
SERVICE_TYPE = "llm"
|
||||
SERVICE_VERSION = ""
|
||||
)
|
||||
|
||||
type LLMBaseListDetails struct {
|
||||
|
||||
@@ -13,15 +13,16 @@ const (
|
||||
LLM_CLIENT_OLLAMA LLMClientType = "ollama"
|
||||
LLM_CLIENT_OPENAI LLMClientType = "openai"
|
||||
|
||||
MCP_AGENT_SYSTEM_PROMPT = `你是一个 Cloudpods 云平台管理助手。你可以使用提供的工具来帮助用户管理云资源。
|
||||
MCP_AGENT_SYSTEM_PROMPT = `你是一个 %s 云平台管理助手。你可以使用提供的工具来帮助用户管理云资源。
|
||||
|
||||
## 你的能力
|
||||
- 查询云平台资源(虚拟机、镜像、网络、存储、区域等)
|
||||
- 查询云平台资源(虚拟机、镜像、网络、存储、区域、套餐等)
|
||||
- 管理虚拟机(创建、启动、停止、重启、删除、重置密码)
|
||||
- 获取虚拟机监控信息和实时统计数据
|
||||
|
||||
## 重要规则(必须严格遵守)
|
||||
**如果用户的问题涉及查询、创建、修改或删除云资源,你必须先调用相应的工具,而不是直接回答。**
|
||||
- 严禁空口编造:在未真正调用工具并拿到返回结果前,禁止声称“已查到区域/镜像/网络”“正在创建成功”等。
|
||||
- 对于需要查询资源的问题(如"列出虚拟机"、"查询状态"等),必须调用工具获取数据后再回答
|
||||
- 对于需要操作资源的问题(如"创建"、"启动"、"停止"等),必须调用工具执行操作后再回答
|
||||
- 只有在以下情况才可以直接回复:
|
||||
@@ -29,9 +30,17 @@ const (
|
||||
2. 没有合适的工具可以解决用户的问题
|
||||
3. 工具调用失败后需要向用户说明错误原因
|
||||
|
||||
## 创建虚拟机标准流程(同一轮对话中连续调用工具,不要只说不做;查询可并行以节省轮次)
|
||||
1. climc_cloud_region_list(公有云必须 provider=["Aliyun"] 等;创建时 usable=true)
|
||||
2. climc_cloud_region_capability(ID=区域 id;从 storage_types2 取系统盘 backend)
|
||||
3. climc_cached_image_list(公有云)或 climc_image_list(KVM);公有云必须带 provider + region=区域 id;不要重复调用
|
||||
4. climc_server_sku_list(公有云带 provider+cloudregion;用户说 2c2g/2核2G 时传 spec="2c2g")
|
||||
5. climc_server_create(name、disk 须含 image+backend、instance-type 或 ncpu/mem-spec;公有云 hypervisor=aliyun、prefer-region=区域 id)。net 可省略:未指定时自动 random(nets:[{exit:false}])调度,默认不要先 network-list/vpc-list
|
||||
查询工具的返回不等于任务完成;必须最终调用 climc_server_create。创建失败时根据工具错误向用户说明原因。
|
||||
|
||||
## 工作流程
|
||||
1. 理解用户的需求
|
||||
2. **优先检查是否有合适的工具可以完成任务,如果有则必须调用工具**
|
||||
2. **优先检查是否有合适的工具可以完成任务,如果有则必须调用工具(发 tool_calls,不要只输出计划文字)**
|
||||
3. 分析工具返回的结果
|
||||
4. 如果需要更多信息,继续调用其他工具
|
||||
5. 最后用自然语言总结结果给用户
|
||||
|
||||
@@ -8,5 +8,5 @@ const (
|
||||
LLM_SGLANG_HF_ENDPOINT = LLM_VLLM_HF_ENDPOINT
|
||||
LLM_SGLANG_CACHE_DIR = "/root/.cache/huggingface"
|
||||
LLM_SGLANG_BASE_PATH = "/data/models"
|
||||
LLM_SGLANG_MODELS_PATH = "/data/models/huggingface"
|
||||
LLM_SGLANG_MODELS_PATH = LLM_SGLANG_BASE_PATH
|
||||
)
|
||||
|
||||
@@ -78,9 +78,14 @@ func (s PortMappings) IsZero() bool {
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
DevType string `json:"dev_type"`
|
||||
Model string `json:"model"`
|
||||
DevicePath string `json:"device_path"`
|
||||
DevType string `json:"dev_type"`
|
||||
SharingMode string `json:"sharing_mode,omitempty"`
|
||||
Model string `json:"model"`
|
||||
DevicePath string `json:"device_path"`
|
||||
// MemoryMb is optional per-device VRAM (MiB) for HAMI. When > 0 it is used
|
||||
// as MemoryMb/MemoryRequest on pod create; otherwise claim is split evenly.
|
||||
MemoryMb int `json:"memory_mb,omitempty"`
|
||||
SmUtilLimit int `json:"sm_util_limit,omitempty"`
|
||||
}
|
||||
|
||||
type Devices []Device
|
||||
@@ -139,6 +144,10 @@ type LLMSkuDetails struct {
|
||||
// Inference backend version and parameters
|
||||
BackendVersion string `json:"backend_version"`
|
||||
BackendParameters []string `json:"backend_parameters,omitempty"`
|
||||
|
||||
// VramClaimMb is computed from mounted InstantModel weight_size_bytes
|
||||
// (EstimateClaimMb). Not persisted on the SKU row.
|
||||
VramClaimMb int `json:"vram_claim_mb"`
|
||||
}
|
||||
|
||||
type MountedAppResourceDetails struct {
|
||||
@@ -151,10 +160,6 @@ type LLMSKuBaseCreateInput struct {
|
||||
Cpu int `json:"cpu"`
|
||||
Memory int `json:"memory"`
|
||||
Bandwidth int `json:"bandwidth"`
|
||||
// VramClaimMb is the estimated VRAM (MiB) the inference instance will
|
||||
// require. Optional — if 0, the deployment create task will auto-fill it
|
||||
// from the mounted InstantModel's weight_size_bytes.
|
||||
VramClaimMb int `json:"vram_claim_mb,omitempty"`
|
||||
|
||||
Volumes *Volumes `json:"volumes"`
|
||||
HostPaths *HostPaths `json:"host_paths"`
|
||||
|
||||
@@ -8,5 +8,5 @@ const (
|
||||
LLM_VLLM_HF_ENDPOINT = "https://hf-mirror.com"
|
||||
LLM_VLLM_CACHE_DIR = "/root/.cache/huggingface"
|
||||
LLM_VLLM_BASE_PATH = "/data/models"
|
||||
LLM_VLLM_MODELS_PATH = "/data/models/huggingface"
|
||||
LLM_VLLM_MODELS_PATH = LLM_VLLM_BASE_PATH
|
||||
)
|
||||
|
||||
@@ -18,8 +18,10 @@ import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"mime"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
)
|
||||
@@ -36,6 +38,8 @@ type responseWriterChannel struct {
|
||||
bodyResp chan responseWriterResponse
|
||||
statusChan chan int
|
||||
statusResp chan bool
|
||||
flushChan chan struct{}
|
||||
flushResp chan struct{}
|
||||
|
||||
isClosed bool
|
||||
}
|
||||
@@ -47,6 +51,8 @@ func newResponseWriterChannel(backend http.ResponseWriter) responseWriterChannel
|
||||
bodyResp: make(chan responseWriterResponse),
|
||||
statusChan: make(chan int),
|
||||
statusResp: make(chan bool),
|
||||
flushChan: make(chan struct{}),
|
||||
flushResp: make(chan struct{}),
|
||||
isClosed: false,
|
||||
}
|
||||
}
|
||||
@@ -76,16 +82,34 @@ func (w *responseWriterChannel) WriteHeader(status int) {
|
||||
<-w.statusResp
|
||||
}
|
||||
|
||||
// implent http.Flusher
|
||||
// Flush implements http.Flusher. Flush must run on the same goroutine as Write
|
||||
// (wait loop); otherwise SSE via ReverseProxy never reaches the client.
|
||||
func (w *responseWriterChannel) Flush() {
|
||||
if w.isClosed {
|
||||
return
|
||||
}
|
||||
w.flushChan <- struct{}{}
|
||||
<-w.flushResp
|
||||
}
|
||||
|
||||
func (w *responseWriterChannel) flushBackend() {
|
||||
if f, ok := w.backend.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *responseWriterChannel) isEventStream() bool {
|
||||
ct := w.backend.Header().Get("Content-Type")
|
||||
if ct == "" {
|
||||
return false
|
||||
}
|
||||
baseCT, _, err := mime.ParseMediaType(ct)
|
||||
if err != nil {
|
||||
return strings.HasPrefix(ct, "text/event-stream")
|
||||
}
|
||||
return baseCT == "text/event-stream"
|
||||
}
|
||||
|
||||
// Hijack implements the Hijacker.Hijack method. Our response is both a ResponseWriter
|
||||
// and a Hijacker.
|
||||
func (w *responseWriterChannel) Hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error) {
|
||||
@@ -121,6 +145,11 @@ func (w *responseWriterChannel) wait(ctx context.Context, workerChan chan *SWork
|
||||
// log.Infof("Recive body: %s, more: %v", len(bytes), more)
|
||||
if more {
|
||||
c, e := w.backend.Write(bytes)
|
||||
// SSE / streaming: flush immediately after each chunk so clients
|
||||
// see events without waiting for handler completion.
|
||||
if e == nil && w.isEventStream() {
|
||||
w.flushBackend()
|
||||
}
|
||||
w.bodyResp <- responseWriterResponse{count: c, err: e}
|
||||
} else {
|
||||
stop = true
|
||||
@@ -133,6 +162,13 @@ func (w *responseWriterChannel) wait(ctx context.Context, workerChan chan *SWork
|
||||
} else {
|
||||
stop = true
|
||||
}
|
||||
case _, more := <-w.flushChan:
|
||||
if more {
|
||||
w.flushBackend()
|
||||
w.flushResp <- struct{}{}
|
||||
} else {
|
||||
stop = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
@@ -148,4 +184,6 @@ func (w *responseWriterChannel) closeChannels() {
|
||||
close(w.bodyResp)
|
||||
close(w.statusChan)
|
||||
close(w.statusResp)
|
||||
close(w.flushChan)
|
||||
close(w.flushResp)
|
||||
}
|
||||
|
||||
@@ -205,7 +205,10 @@ func handleBaremetalValidateIPMI() appsrv.FilterHandler {
|
||||
if redfishCli == nil {
|
||||
resp.IsRedfishSupported = false
|
||||
// use ipmitool to validate
|
||||
tool := ipmitool.NewLanPlusIPMI(input.Ip, input.Username, input.Password)
|
||||
tool, err := ipmitool.NewLanPlusIPMI(input.Ip, input.Username, input.Password)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "NewLanPlusIPMI")
|
||||
}
|
||||
info, err := ipmitool.GetSysInfo(tool)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetSysInfo by ipmitool")
|
||||
|
||||
@@ -498,7 +498,10 @@ func (m *SBaremetalManager) checkSshInfo(input *BmRegisterInput) (*ssh.Client, e
|
||||
}
|
||||
|
||||
func (m *SBaremetalManager) checkIpmiInfo(ctx context.Context, username, password, ipAddr string) (uint8, net.HardwareAddr, error) {
|
||||
lanPlusTool := ipmitool.NewLanPlusIPMI(ipAddr, username, password)
|
||||
lanPlusTool, err := ipmitool.NewLanPlusIPMI(ipAddr, username, password)
|
||||
if err != nil {
|
||||
return 0, nil, errors.Wrap(err, "NewLanPlusIPMI")
|
||||
}
|
||||
sysInfo, err := ipmitool.GetSysInfo(lanPlusTool)
|
||||
if err != nil {
|
||||
return 0, nil, errors.Wrap(err, "GetSysInfo")
|
||||
@@ -1823,7 +1826,12 @@ func (b *SBaremetalInstance) GetIPMITool() *ipmitool.LanPlusIPMI {
|
||||
log.Debugf("GetIPMIConfig is nil")
|
||||
return nil
|
||||
}
|
||||
return ipmitool.NewLanPlusIPMI(conf.IpAddr, conf.Username, conf.Password)
|
||||
tool, err := ipmitool.NewLanPlusIPMIWithCipher(conf.IpAddr, conf.Username, conf.Password, 623, conf.CipherSuite)
|
||||
if err != nil {
|
||||
log.Errorf("NewLanPlusIPMIWithCipher for %s: %v", conf.IpAddr, err)
|
||||
return nil
|
||||
}
|
||||
return tool
|
||||
}
|
||||
|
||||
func (b *SBaremetalInstance) isRedfishCapable() bool {
|
||||
|
||||
@@ -537,7 +537,11 @@ func (task *sBaremetalPrepareTask) tryLocalIpmiAddr(ctx context.Context, sshIPMI
|
||||
log.Errorf("Failed to get lan config after %d tries", tried)
|
||||
return false
|
||||
}
|
||||
rmcpIPMI := ipmitool.NewLanPlusIPMI(tryAddr, ipmiUser, ipmiPasswd)
|
||||
rmcpIPMI, err := ipmitool.NewLanPlusIPMI(tryAddr, ipmiUser, ipmiPasswd)
|
||||
if err != nil {
|
||||
log.Errorf("NewLanPlusIPMI for %s: %v", tryAddr, err)
|
||||
return false
|
||||
}
|
||||
for tried = 0; tried < maxTries; tried += 1 {
|
||||
conf2, err := ipmitool.GetLanConfig(rmcpIPMI, lanChannel)
|
||||
if err != nil {
|
||||
@@ -849,7 +853,7 @@ func (task *sBaremetalPrepareTask) sendIsolatedDevicesInfo(
|
||||
|
||||
gpuDevs := make([]isolated_device.IDevice, len(devs))
|
||||
for i := 0; i < len(devs); i++ {
|
||||
gpuDevs[i] = isolated_device.NewGPUHPCDevice(devs[i])
|
||||
gpuDevs[i] = isolated_device.NewGPUHPCDevice(devs[i], api.DEVICE_SHARING_MODE_EXCLUSIVE)
|
||||
}
|
||||
|
||||
for _, obj := range objs {
|
||||
|
||||
@@ -85,7 +85,10 @@ func (self *SBaremetalIpmiProbeTask) DoIpmiProbe(ctx context.Context, args inter
|
||||
// else, redfish call fails, try IPMI
|
||||
} */
|
||||
log.Warningf("BMC not redfish-compatible for IPMI: %s, use raw probe", ipmiInfo.IpAddr)
|
||||
ipmiTool := ipmitool.NewLanPlusIPMI(ipmiInfo.IpAddr, ipmiInfo.Username, ipmiInfo.Password)
|
||||
ipmiTool, err := ipmitool.NewLanPlusIPMI(ipmiInfo.IpAddr, ipmiInfo.Username, ipmiInfo.Password)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "NewLanPlusIPMI")
|
||||
}
|
||||
return self.doRawIpmiProbe(ctx, ipmiTool)
|
||||
}
|
||||
|
||||
@@ -254,6 +257,9 @@ func (self *SBaremetalIpmiProbeTask) doRawIpmiProbe(ctx context.Context, cli ipm
|
||||
ipmiInfo.CdromBoot = false
|
||||
ipmiInfo.PxeBoot = o.Options.EnablePxeBoot
|
||||
ipmiInfo.LanChannel = channel
|
||||
if lanPlus, ok := cli.(*ipmitool.LanPlusIPMI); ok {
|
||||
ipmiInfo.CipherSuite = lanPlus.GetCipherSuite()
|
||||
}
|
||||
updateData := jsonutils.Marshal(updateInfo)
|
||||
updateData.(*jsonutils.JSONDict).Update(ipmiInfo.ToPrepareParams())
|
||||
_, err = modules.Hosts.Update(self.Baremetal.GetClientSession(), self.Baremetal.GetId(), updateData)
|
||||
|
||||
@@ -89,31 +89,60 @@ func (ipmi *SSHIPMI) ExecuteCommand(args ...string) ([]string, error) {
|
||||
return ipmi.sshClient.Run(cmd.String())
|
||||
}
|
||||
|
||||
// DefaultCipherSuites is the probe order for RMCP+ cipher suites.
|
||||
// 0 means do not pass -C (ipmitool default); 3 and 17 are common BMC requirements.
|
||||
var DefaultCipherSuites = []int{0, 3, 17}
|
||||
|
||||
type LanPlusIPMI struct {
|
||||
IPMIParser
|
||||
host string
|
||||
user string
|
||||
password string
|
||||
port int
|
||||
host string
|
||||
user string
|
||||
password string
|
||||
port int
|
||||
cipherSuite int // 0 = no -C; >0 = pass -C N
|
||||
cipherResolved bool // true after Ensure/Detect or constructed with known suite > 0
|
||||
}
|
||||
|
||||
func NewLanPlusIPMI(host, user, password string) *LanPlusIPMI {
|
||||
func NewLanPlusIPMI(host, user, password string) (*LanPlusIPMI, error) {
|
||||
return NewLanPlusIPMIWithPort(host, user, password, 623)
|
||||
}
|
||||
|
||||
func NewLanPlusIPMIWithPort(host, user, password string, port int) *LanPlusIPMI {
|
||||
return &LanPlusIPMI{
|
||||
host: host,
|
||||
user: user,
|
||||
password: password,
|
||||
port: port,
|
||||
func NewLanPlusIPMIWithPort(host, user, password string, port int) (*LanPlusIPMI, error) {
|
||||
return NewLanPlusIPMIWithCipher(host, user, password, port, 0)
|
||||
}
|
||||
|
||||
func NewLanPlusIPMIWithCipher(host, user, password string, port, cipherSuite int) (*LanPlusIPMI, error) {
|
||||
ipmi := &LanPlusIPMI{
|
||||
host: host,
|
||||
user: user,
|
||||
password: password,
|
||||
port: port,
|
||||
cipherSuite: cipherSuite,
|
||||
}
|
||||
// Known non-default suite from persisted config: skip re-detect.
|
||||
if cipherSuite > 0 {
|
||||
ipmi.cipherResolved = true
|
||||
return ipmi, nil
|
||||
}
|
||||
if err := ipmi.ensureCipherSuite(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ipmi, nil
|
||||
}
|
||||
|
||||
func (ipmi *LanPlusIPMI) GetMode() string {
|
||||
return "rmcp"
|
||||
}
|
||||
|
||||
func (ipmi *LanPlusIPMI) SetCipherSuite(suite int) {
|
||||
ipmi.cipherSuite = suite
|
||||
ipmi.cipherResolved = true
|
||||
}
|
||||
|
||||
func (ipmi *LanPlusIPMI) GetCipherSuite() int {
|
||||
return ipmi.cipherSuite
|
||||
}
|
||||
|
||||
func (ipmi *LanPlusIPMI) GetCommand(args ...string) (*procutils.Command, context.CancelFunc) {
|
||||
nArgs := []string{
|
||||
"-I", "lanplus", "-H", ipmi.host,
|
||||
@@ -121,6 +150,9 @@ func (ipmi *LanPlusIPMI) GetCommand(args ...string) (*procutils.Command, context
|
||||
"-U", ipmi.user,
|
||||
"-P", ipmi.password,
|
||||
}
|
||||
if ipmi.cipherSuite > 0 {
|
||||
nArgs = append(nArgs, "-C", strconv.Itoa(ipmi.cipherSuite))
|
||||
}
|
||||
nArgs = append(nArgs, args...)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), ipmi.GetDefaultTimeout())
|
||||
return procutils.NewCommandContext(ctx, "ipmitool", nArgs...), cancel
|
||||
@@ -137,6 +169,34 @@ func (ipmi *LanPlusIPMI) ExecuteCommand(args ...string) ([]string, error) {
|
||||
return ssh.ParseOutput(out), nil
|
||||
}
|
||||
|
||||
// DetectCipherSuite tries DefaultCipherSuites with a single chassis power status each.
|
||||
// On success it sets the working suite on the receiver and returns it.
|
||||
func (ipmi *LanPlusIPMI) DetectCipherSuite() (int, error) {
|
||||
var errs []error
|
||||
for _, suite := range DefaultCipherSuites {
|
||||
ipmi.cipherSuite = suite
|
||||
ipmi.cipherResolved = false
|
||||
_, err := ipmi.ExecuteCommand("chassis", "power", "status")
|
||||
if err == nil {
|
||||
ipmi.SetCipherSuite(suite)
|
||||
log.Infof("[LanPlusIPMI] detected cipher suite %d for %s", suite, ipmi.host)
|
||||
return suite, nil
|
||||
}
|
||||
errs = append(errs, errors.Wrapf(err, "cipher suite %d", suite))
|
||||
log.Debugf("[LanPlusIPMI] cipher suite %d failed for %s: %v", suite, ipmi.host, err)
|
||||
}
|
||||
return 0, errors.Wrapf(errors.NewAggregate(errs), "detect cipher suite for %s", ipmi.host)
|
||||
}
|
||||
|
||||
// ensureCipherSuite uses a known suite when already resolved; otherwise runs DetectCipherSuite.
|
||||
func (ipmi *LanPlusIPMI) ensureCipherSuite() error {
|
||||
if ipmi.cipherResolved {
|
||||
return nil
|
||||
}
|
||||
_, err := ipmi.DetectCipherSuite()
|
||||
return err
|
||||
}
|
||||
|
||||
func GetSysGuid(exector IPMIExecutor) string {
|
||||
args := []string{"mc", "guid"}
|
||||
// args := []string{"raw", "0x06", "0x37"}
|
||||
|
||||
@@ -513,8 +513,7 @@ func ParseIsolatedDevice(desc string, idx int) (*compute.IsolatedDeviceConfig, e
|
||||
}
|
||||
dev := new(compute.IsolatedDeviceConfig)
|
||||
parts := strings.Split(desc, ":")
|
||||
devTypes := sets.NewString(compute.VALID_PASSTHROUGH_TYPES...)
|
||||
devTypes.Insert(compute.VALID_CONTAINER_DEVICE_TYPES...)
|
||||
devTypes := sets.NewString(compute.VALID_TYPES...)
|
||||
for _, p := range parts {
|
||||
if regutils.MatchUUIDExact(p) {
|
||||
dev.Id = p
|
||||
|
||||
@@ -175,6 +175,7 @@ type HostCommonOptions struct {
|
||||
ImageDeployDriver string `help:"Image deploy driver" default:"qemu-kvm" choices:"qemu-kvm|nbd|libguestfs"`
|
||||
DeployConcurrent int `help:"qemu-kvm deploy driver concurrent" default:"5"`
|
||||
Qcow2Preallocation string `help:"Qcow2 image create preallocation" default:"metadata" choices:"disable|metadata|falloc|full"`
|
||||
EnableNestedVirtualization bool `help:"Enable nested virtualization" default:"true"`
|
||||
}
|
||||
|
||||
type S3CommonOptions struct {
|
||||
|
||||
@@ -24,15 +24,16 @@ const (
|
||||
)
|
||||
|
||||
type SIPMIInfo struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
IpAddr string `json:"ip_addr,omitempty"`
|
||||
Present bool `json:"present,omitempty"`
|
||||
LanChannel uint8 `json:"lan_channel,omitzero"`
|
||||
Verified bool `json:"verified,omitfalse"`
|
||||
RedfishApi bool `json:"redfish_api,omitfalse"`
|
||||
CdromBoot bool `json:"cdrom_boot,omitfalse"`
|
||||
PxeBoot bool `json:"pxe_boot,omitfalse"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
IpAddr string `json:"ip_addr,omitempty"`
|
||||
Present bool `json:"present,omitempty"`
|
||||
LanChannel uint8 `json:"lan_channel,omitzero"`
|
||||
CipherSuite int `json:"cipher_suite,omitzero"`
|
||||
Verified bool `json:"verified,omitfalse"`
|
||||
RedfishApi bool `json:"redfish_api,omitfalse"`
|
||||
CdromBoot bool `json:"cdrom_boot,omitfalse"`
|
||||
PxeBoot bool `json:"pxe_boot,omitfalse"`
|
||||
}
|
||||
|
||||
func (info SIPMIInfo) ToPrepareParams() jsonutils.JSONObject {
|
||||
@@ -48,6 +49,9 @@ func (info SIPMIInfo) ToPrepareParams() jsonutils.JSONObject {
|
||||
}
|
||||
data.Add(jsonutils.NewBool(info.Present), "ipmi_present")
|
||||
data.Add(jsonutils.NewInt(int64(info.LanChannel)), "ipmi_lan_channel")
|
||||
if info.CipherSuite > 0 {
|
||||
data.Add(jsonutils.NewInt(int64(info.CipherSuite)), "ipmi_cipher_suite")
|
||||
}
|
||||
if info.Verified {
|
||||
data.Add(jsonutils.JSONTrue, "ipmi_verified")
|
||||
}
|
||||
|
||||
@@ -692,25 +692,34 @@ func (self *SCloudgroup) PerformAttachPolicy(ctx context.Context, userCred mccli
|
||||
if self.Status != apis.STATUS_AVAILABLE {
|
||||
return nil, httperrors.NewInvalidStatusError("Can not attach policy in status %s", self.Status)
|
||||
}
|
||||
policyObj, err := validators.ValidateModel(ctx, userCred, CloudpolicyManager, &input.CloudpolicyId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
policyIds := input.CloudpolicyIds
|
||||
if len(input.CloudpolicyId) > 0 {
|
||||
policyIds = append(policyIds, input.CloudpolicyId)
|
||||
}
|
||||
policy := policyObj.(*SCloudpolicy)
|
||||
if policy.ManagerId != self.ManagerId || policy.CloudaccountId != self.CloudaccountId {
|
||||
return nil, httperrors.NewConflictError("policy and groups do not belong to the same account")
|
||||
}
|
||||
_, err = self.GetCloudpolicy(input.CloudpolicyId)
|
||||
if err == nil || errors.Cause(err) == sqlchemy.ErrDuplicateEntry {
|
||||
return nil, httperrors.NewDuplicateResourceError("policy %s has aleady in this group", input.CloudpolicyId)
|
||||
if len(policyIds) == 0 {
|
||||
return nil, httperrors.NewMissingParameterError("cloudpolicy_ids")
|
||||
}
|
||||
|
||||
add := []api.SPolicy{
|
||||
{
|
||||
add := []api.SPolicy{}
|
||||
for i := range policyIds {
|
||||
policyObj, err := validators.ValidateModel(ctx, userCred, CloudpolicyManager, &policyIds[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policy := policyObj.(*SCloudpolicy)
|
||||
if policy.ManagerId != self.ManagerId || policy.CloudaccountId != self.CloudaccountId {
|
||||
return nil, httperrors.NewConflictError("policy and groups do not belong to the same account")
|
||||
}
|
||||
_, err = self.GetCloudpolicy(policy.Id)
|
||||
if err == nil || errors.Cause(err) == sqlchemy.ErrDuplicateEntry {
|
||||
return nil, httperrors.NewDuplicateResourceError("policy %s has aleady in this group", policy.Id)
|
||||
}
|
||||
add = append(add, api.SPolicy{
|
||||
Name: policy.Name,
|
||||
ExternalId: policy.ExternalId,
|
||||
PolicyType: policy.PolicyType,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return nil, self.StartSetPoliciesTask(ctx, userCred, add, nil, "")
|
||||
@@ -723,23 +732,35 @@ func (self *SCloudgroup) PerformDetachPolicy(ctx context.Context, userCred mccli
|
||||
return nil, httperrors.NewInvalidStatusError("Can not detach policy in status %s", self.Status)
|
||||
}
|
||||
|
||||
policObj, err := validators.ValidateModel(ctx, userCred, CloudpolicyManager, &input.CloudpolicyId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
policyIds := input.CloudpolicyIds
|
||||
if len(input.CloudpolicyId) > 0 {
|
||||
policyIds = append(policyIds, input.CloudpolicyId)
|
||||
}
|
||||
policy := policObj.(*SCloudpolicy)
|
||||
|
||||
_, err = self.GetCloudpolicy(input.CloudpolicyId)
|
||||
if err != nil && errors.Cause(err) == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
if len(policyIds) == 0 {
|
||||
return nil, httperrors.NewMissingParameterError("cloudpolicy_ids")
|
||||
}
|
||||
|
||||
del := []api.SPolicy{
|
||||
{
|
||||
del := []api.SPolicy{}
|
||||
for i := range policyIds {
|
||||
policObj, err := validators.ValidateModel(ctx, userCred, CloudpolicyManager, &policyIds[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policy := policObj.(*SCloudpolicy)
|
||||
|
||||
_, err = self.GetCloudpolicy(policy.Id)
|
||||
if err != nil && errors.Cause(err) == sql.ErrNoRows {
|
||||
continue
|
||||
}
|
||||
|
||||
del = append(del, api.SPolicy{
|
||||
Name: policy.Name,
|
||||
ExternalId: policy.ExternalId,
|
||||
PolicyType: policy.PolicyType,
|
||||
},
|
||||
})
|
||||
}
|
||||
if len(del) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, self.StartSetPoliciesTask(ctx, userCred, nil, del, "")
|
||||
|
||||
@@ -49,6 +49,10 @@ func (set ProxyEndpoints) Copy() apihelper.IModelSet {
|
||||
return setCopy
|
||||
}
|
||||
|
||||
func (set ProxyEndpoints) IncludeDetails() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (ms ProxyEndpoints) joinForwards(subEntries Forwards) bool {
|
||||
correct := true
|
||||
for _, subEntry := range subEntries {
|
||||
@@ -88,3 +92,7 @@ func (set Forwards) Copy() apihelper.IModelSet {
|
||||
}
|
||||
return setCopy
|
||||
}
|
||||
|
||||
func (set Forwards) IncludeDetails() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func (h hostDevice) ValidateCreateData(ctx context.Context, userCred mcclient.To
|
||||
return dev, nil
|
||||
}
|
||||
|
||||
func (h hostDevice) ToHostDevice(dev *api.ContainerDevice) (*hostapi.ContainerDevice, error) {
|
||||
func (h hostDevice) ToHostDevice(dev *api.ContainerDevice, guestId string) (*hostapi.ContainerDevice, error) {
|
||||
return &hostapi.ContainerDevice{
|
||||
Type: apis.CONTAINER_DEVICE_TYPE_HOST,
|
||||
ContainerPath: dev.Host.ContainerPath,
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/sets"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
@@ -87,7 +86,7 @@ func (i isolatedDevice) ValidateCreateData(ctx context.Context, userCred mcclien
|
||||
return nil, errors.Wrapf(err, "validate create data %s", jsonutils.Marshal(dev))
|
||||
}
|
||||
isoDev := dev.IsolatedDevice
|
||||
podDevs, err := pod.GetIsolatedDevices()
|
||||
podDevs, err := pod.GetGuestIsolatedDevices()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get isolated devices")
|
||||
}
|
||||
@@ -96,7 +95,8 @@ func (i isolatedDevice) ValidateCreateData(ctx context.Context, userCred mcclien
|
||||
if index >= len(podDevs) {
|
||||
return nil, httperrors.NewInputParameterError("index %d is large than isolated device size %d", index, len(podDevs))
|
||||
}
|
||||
isoDev.Id = podDevs[index].GetId()
|
||||
isoDev.Id = podDevs[index].IsolatedDeviceId
|
||||
isoDev.GuestIsolatedDeviceIndex = int(podDevs[index].Index)
|
||||
// remove index
|
||||
isoDev.Index = nil
|
||||
} else {
|
||||
@@ -104,13 +104,15 @@ func (i isolatedDevice) ValidateCreateData(ctx context.Context, userCred mcclien
|
||||
return nil, httperrors.NewNotEmptyError("id is empty")
|
||||
}
|
||||
foundDisk := false
|
||||
for _, d := range podDevs {
|
||||
for i := range podDevs {
|
||||
d := podDevs[i].GetIsolatedDevice()
|
||||
if d.GetId() == isoDev.Id || d.GetName() == isoDev.Id {
|
||||
isoDev.Id = d.GetId()
|
||||
isoDev.GuestIsolatedDeviceIndex = int(podDevs[i].Index)
|
||||
foundDisk = true
|
||||
devType := d.DevType
|
||||
if !sets.NewString(api.VALID_CONTAINER_DEVICE_TYPES...).Has(devType) {
|
||||
return nil, httperrors.NewInputParameterError("device type %s is not supported by container", devType)
|
||||
host := d.GetHost()
|
||||
if host.HostType != api.HOST_TYPE_CONTAINER {
|
||||
return nil, httperrors.NewInputParameterError("device %s is not supported by container", isoDev.Id)
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -123,13 +125,17 @@ func (i isolatedDevice) ValidateCreateData(ctx context.Context, userCred mcclien
|
||||
return dev, nil
|
||||
}
|
||||
|
||||
func (i isolatedDevice) ToHostDevice(dev *api.ContainerDevice) (*hostapi.ContainerDevice, error) {
|
||||
func (i isolatedDevice) ToHostDevice(dev *api.ContainerDevice, guestId string) (*hostapi.ContainerDevice, error) {
|
||||
input := dev.IsolatedDevice
|
||||
isoDevObj, err := models.IsolatedDeviceManager.FetchById(input.Id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Fetch isolated device by id %s", input.Id)
|
||||
}
|
||||
isoDev := isoDevObj.(*models.SIsolatedDevice)
|
||||
gdev, err := isoDev.GetGuestIsolatedDevice(guestId, input.GuestIsolatedDeviceIndex)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetGuestIsolatedDevice")
|
||||
}
|
||||
return &hostapi.ContainerDevice{
|
||||
Type: dev.Type,
|
||||
IsolatedDevice: &hostapi.ContainerIsolatedDevice{
|
||||
@@ -138,7 +144,10 @@ func (i isolatedDevice) ToHostDevice(dev *api.ContainerDevice) (*hostapi.Contain
|
||||
Path: isoDev.DevicePath,
|
||||
CardPath: isoDev.CardPath,
|
||||
DeviceType: isoDev.DevType,
|
||||
SharingMode: isoDev.SharingMode,
|
||||
RenderPath: isoDev.RenderPath,
|
||||
MemoryLimit: gdev.DeviceMemorySize,
|
||||
SmUtilLimit: gdev.SmUtilLimit,
|
||||
Index: isoDev.Index,
|
||||
DeviceMinor: isoDev.DeviceMinor,
|
||||
OnlyEnv: input.OnlyEnv,
|
||||
|
||||
@@ -710,11 +710,11 @@ func (base *SBaseGuestDriver) ValidateGuestHotChangeConfigInput(ctx context.Cont
|
||||
return confs, nil
|
||||
}
|
||||
|
||||
func (base *SBaseGuestDriver) BeforeDetachIsolatedDevice(ctx context.Context, cred mcclient.TokenCredential, guest *models.SGuest, dev *models.SIsolatedDevice) error {
|
||||
func (base *SBaseGuestDriver) BeforeDetachIsolatedDevice(ctx context.Context, cred mcclient.TokenCredential, guest *models.SGuest, dev *models.SGuestIsolatedDevice) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (base *SBaseGuestDriver) BeforeAttachIsolatedDevice(ctx context.Context, cred mcclient.TokenCredential, guest *models.SGuest, dev *models.SIsolatedDevice) error {
|
||||
func (base *SBaseGuestDriver) BeforeAttachIsolatedDevice(ctx context.Context, cred mcclient.TokenCredential, guest *models.SGuest, dev *models.SGuestIsolatedDevice) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -851,7 +851,7 @@ func (self *SKVMGuestDriver) CheckMigrate(ctx context.Context, guest *models.SGu
|
||||
}
|
||||
}
|
||||
}
|
||||
devices, err := guest.GetIsolatedDevices()
|
||||
devices, err := guest.GetGuestIsolatedDevices()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetIsolatedDevices")
|
||||
}
|
||||
@@ -879,7 +879,7 @@ func (self *SKVMGuestDriver) CheckLiveMigrate(ctx context.Context, guest *models
|
||||
if cdrom != nil && len(cdrom.ImageId) > 0 {
|
||||
return httperrors.NewBadRequestError("Cannot live migrate with cdrom")
|
||||
}
|
||||
devices, err := guest.GetIsolatedDevices()
|
||||
devices, err := guest.GetGuestIsolatedDevices()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetIsolatedDevices")
|
||||
}
|
||||
|
||||
@@ -646,12 +646,13 @@ func (drv *SManagedVirtualizedGuestDriver) RemoteDeployGuestForCreate(ctx contex
|
||||
}
|
||||
}
|
||||
|
||||
devs, err := guest.GetIsolatedDevices()
|
||||
devs, err := guest.GetGuestIsolatedDevices()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "GetIsolatedDevices")
|
||||
}
|
||||
desc.IsolateDevices = []cloudprovider.SIsolateDevice{}
|
||||
for _, dev := range devs {
|
||||
for i := range devs {
|
||||
dev := devs[i].GetIsolatedDevice()
|
||||
desc.IsolateDevices = append(desc.IsolateDevices, cloudprovider.SIsolateDevice{
|
||||
Id: dev.ExternalId,
|
||||
Name: dev.Name,
|
||||
|
||||
@@ -608,7 +608,7 @@ func (p *SPodDriver) RequestDeleteSnapshot(ctx context.Context, guest *models.SG
|
||||
return p.SKVMGuestDriver.RequestDeleteSnapshot(ctx, guest, task, params)
|
||||
}
|
||||
|
||||
func (p *SPodDriver) BeforeDetachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, dev *models.SIsolatedDevice) error {
|
||||
func (p *SPodDriver) BeforeDetachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, gdev *models.SGuestIsolatedDevice) error {
|
||||
ctrs, err := models.GetContainerManager().GetContainersByPod(guest.GetId())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "get containers by pod %s", guest.GetId())
|
||||
@@ -620,10 +620,11 @@ func (p *SPodDriver) BeforeDetachIsolatedDevice(ctx context.Context, userCred mc
|
||||
newDevs := make([]*api.ContainerDevice, 0)
|
||||
releasedDevs := make(map[string]models.ContainerReleasedDevice)
|
||||
for _, curDev := range devs {
|
||||
if curDev.IsolatedDevice == nil || curDev.IsolatedDevice.Id != dev.GetId() {
|
||||
if curDev.IsolatedDevice == nil || (curDev.IsolatedDevice.Id != gdev.IsolatedDeviceId && curDev.IsolatedDevice.GuestIsolatedDeviceIndex == int(gdev.Index)) {
|
||||
tmpDev := curDev
|
||||
newDevs = append(newDevs, tmpDev)
|
||||
} else {
|
||||
dev := gdev.GetIsolatedDevice()
|
||||
releasedDevs[curDev.IsolatedDevice.Id] = *models.NewContainerReleasedDevice(curDev, dev.DevType, dev.Model)
|
||||
}
|
||||
}
|
||||
@@ -640,7 +641,7 @@ func (p *SPodDriver) BeforeDetachIsolatedDevice(ctx context.Context, userCred mc
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SPodDriver) BeforeAttachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, dev *models.SIsolatedDevice) error {
|
||||
func (p *SPodDriver) BeforeAttachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, dev *models.SGuestIsolatedDevice) error {
|
||||
ctrs, err := models.GetContainerManager().GetContainersByPod(guest.GetId())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "get containers by pod %s", guest.GetId())
|
||||
@@ -654,7 +655,7 @@ func (p *SPodDriver) BeforeAttachIsolatedDevice(ctx context.Context, userCred mc
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SPodDriver) attachIsolatedDeviceToContainer(ctx context.Context, userCred mcclient.TokenCredential, ctrPtr *models.SContainer, dev *models.SIsolatedDevice) error {
|
||||
func (p *SPodDriver) attachIsolatedDeviceToContainer(ctx context.Context, userCred mcclient.TokenCredential, ctrPtr *models.SContainer, gdev *models.SGuestIsolatedDevice) error {
|
||||
rlsDevs, err := ctrPtr.GetReleasedDevices(ctx, userCred)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "get release devices for container %s", ctrPtr.GetId())
|
||||
@@ -672,7 +673,7 @@ func (p *SPodDriver) attachIsolatedDeviceToContainer(ctx context.Context, userCr
|
||||
if curDev.IsolatedDevice == nil {
|
||||
continue
|
||||
}
|
||||
if curDev.IsolatedDevice.Id == dev.GetId() {
|
||||
if curDev.IsolatedDevice.Id == gdev.IsolatedDeviceId && curDev.IsolatedDevice.GuestIsolatedDeviceIndex == int(gdev.Index) {
|
||||
shouldUpdate = false
|
||||
break
|
||||
}
|
||||
@@ -681,7 +682,8 @@ func (p *SPodDriver) attachIsolatedDeviceToContainer(ctx context.Context, userCr
|
||||
spec.Devices = append(spec.Devices, &api.ContainerDevice{
|
||||
Type: apis.CONTAINER_DEVICE_TYPE_ISOLATED_DEVICE,
|
||||
IsolatedDevice: &api.ContainerIsolatedDevice{
|
||||
Id: dev.GetId(),
|
||||
Id: gdev.IsolatedDeviceId,
|
||||
GuestIsolatedDeviceIndex: int(gdev.Index),
|
||||
},
|
||||
})
|
||||
if _, err := db.Update(ctrPtr, func() error {
|
||||
@@ -695,6 +697,7 @@ func (p *SPodDriver) attachIsolatedDeviceToContainer(ctx context.Context, userCr
|
||||
if rlsDev.IsolatedDevice == nil {
|
||||
continue
|
||||
}
|
||||
dev := gdev.GetIsolatedDevice()
|
||||
if rlsDev.DeviceModel == dev.Model && rlsDev.DeviceType == dev.DevType {
|
||||
delete(rlsDevs, id)
|
||||
if err := ctrPtr.SaveReleasedDevices(ctx, userCred, rlsDevs); err != nil {
|
||||
|
||||
@@ -119,9 +119,9 @@ func (self *SVirtualizedGuestDriver) Attach2RandomNetwork(guest *models.SGuest,
|
||||
dev, _ := idev.(*models.SIsolatedDevice)
|
||||
sriovWires = []string{dev.WireId}
|
||||
} else {
|
||||
wires, err := models.IsolatedDeviceManager.FindUnusedNicWiresByModel(netConfig.SriovDevice.Model)
|
||||
wires, err := models.IsolatedDeviceManager.FindAvailableNicWiresByModel(netConfig.SriovDevice.Model)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "FindUnusedNicWiresByModel")
|
||||
return nil, errors.Wrap(err, "FindAvailableNicWiresByModel")
|
||||
}
|
||||
sriovWires = wires
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ type SAccessGroupRule struct {
|
||||
// 云上Id, 对应云上资源自身Id
|
||||
ExternalId string `width:"256" charset:"utf8" index:"true" list:"user" create:"domain_optional" update:"admin" json:"external_id"`
|
||||
|
||||
Priority int `default:"1" list:"user" update:"user" list:"user"`
|
||||
Priority int `default:"1" list:"user" update:"user"`
|
||||
Source string `width:"16" charset:"ascii" list:"user" update:"user" create:"required"`
|
||||
RWAccessType string `width:"16" charset:"ascii" list:"user" update:"user" create:"required"`
|
||||
UserAccessType string `width:"16" charset:"ascii" list:"user" update:"user" create:"required"`
|
||||
|
||||
@@ -869,9 +869,11 @@ func getStorageTypes(
|
||||
}
|
||||
|
||||
type PCIDevModelTypes struct {
|
||||
Model string
|
||||
DevType string
|
||||
SizeMB int
|
||||
Model string
|
||||
DevType string
|
||||
SharingMode string
|
||||
NvmeSizeMB int
|
||||
DevMemorySize int
|
||||
|
||||
VirtualDev bool
|
||||
Hypervisor string
|
||||
@@ -885,7 +887,7 @@ func getIsolatedDeviceInfo(ctx context.Context, userCred mcclient.TokenCredentia
|
||||
hostQuery = StorageManager.FilterByOwner(ctx, hostQuery, StorageManager, userCred, ownerId, rbacscope.ScopeDomain)
|
||||
}
|
||||
if len(tenantId) > 0 {
|
||||
devicesQ = devicesQ.IsNullOrEmpty("guest_id")
|
||||
devicesQ = IsolatedDeviceManager.GetAvailableIsolatedDeviceQuery(devicesQ)
|
||||
subq := db.SharedResourceManager.Query("resource_id")
|
||||
subq = subq.Equals("resource_type", IsolatedDeviceManager.Keyword())
|
||||
subq = subq.Equals("target_project_id", tenantId)
|
||||
@@ -916,7 +918,7 @@ func getIsolatedDeviceInfo(ctx context.Context, userCred mcclient.TokenCredentia
|
||||
devices := devicesQ.SubQuery()
|
||||
hosts := hostQuery.SubQuery()
|
||||
|
||||
q := devices.Query(hosts.Field("host_type"), devices.Field("model"), devices.Field("dev_type"), devices.Field("nvme_size_mb"))
|
||||
q := devices.Query(hosts.Field("host_type"), devices.Field("model"), devices.Field("dev_type"), devices.Field("sharing_mode"), devices.Field("nvme_size_mb"), devices.Field("memory_size"))
|
||||
q = q.Filter(sqlchemy.NotIn(devices.Field("dev_type"), []string{api.USB_TYPE, api.NIC_TYPE}))
|
||||
if zone != nil {
|
||||
q = q.Join(hosts, sqlchemy.Equals(devices.Field("host_id"), hosts.Field("id")))
|
||||
@@ -935,7 +937,7 @@ func getIsolatedDeviceInfo(ctx context.Context, userCred mcclient.TokenCredentia
|
||||
sqlchemy.IsNullOrEmpty(hosts.Field("manager_id")),
|
||||
))
|
||||
}*/
|
||||
q = q.GroupBy(hosts.Field("host_type"), devices.Field("model"), devices.Field("dev_type"), devices.Field("nvme_size_mb"))
|
||||
q = q.GroupBy(hosts.Field("host_type"), devices.Field("model"), devices.Field("dev_type"), devices.Field("sharing_mode"), devices.Field("nvme_size_mb"), devices.Field("memory_size"))
|
||||
|
||||
rows, err := q.Rows()
|
||||
if err != nil {
|
||||
@@ -946,20 +948,21 @@ func getIsolatedDeviceInfo(ctx context.Context, userCred mcclient.TokenCredentia
|
||||
gpus := make([]PCIDevModelTypes, 0)
|
||||
gpuModels := make([]string, 0)
|
||||
for rows.Next() {
|
||||
var m, t string
|
||||
var sizeMB int
|
||||
var m, t, sharingMode string
|
||||
var nvmeSizeMB int
|
||||
var memSizeMB int
|
||||
var vdev bool
|
||||
var hypervisor string
|
||||
var hostType string
|
||||
rows.Scan(&hostType, &m, &t, &sizeMB)
|
||||
rows.Scan(&hostType, &m, &t, &sharingMode, &nvmeSizeMB, &memSizeMB)
|
||||
|
||||
if m == "" {
|
||||
continue
|
||||
}
|
||||
if utils.IsInStringArray(t, api.VITRUAL_DEVICE_TYPES) {
|
||||
if utils.IsInStringArray(sharingMode, api.VIRTUAL_SHARING_MODES) {
|
||||
vdev = true
|
||||
}
|
||||
if utils.IsInStringArray(t, api.VALID_CONTAINER_DEVICE_TYPES) {
|
||||
if hostType == api.HOST_TYPE_CONTAINER {
|
||||
hypervisor = api.HYPERVISOR_POD
|
||||
} else {
|
||||
hypervisor = api.HYPERVISOR_KVM
|
||||
@@ -969,7 +972,7 @@ func getIsolatedDeviceInfo(ctx context.Context, userCred mcclient.TokenCredentia
|
||||
hypervisor = api.HYPERVISOR_ZETTAKIT
|
||||
}
|
||||
|
||||
gpus = append(gpus, PCIDevModelTypes{m, t, sizeMB, vdev, hypervisor})
|
||||
gpus = append(gpus, PCIDevModelTypes{m, t, sharingMode, nvmeSizeMB, memSizeMB, vdev, hypervisor})
|
||||
|
||||
if !utils.IsInStringArray(m, gpuModels) {
|
||||
gpuModels = append(gpuModels, m)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user