初始提交:OCI 面板后端(含 GenAI 网关一期)

This commit is contained in:
Wang Defa
2026-07-09 15:31:04 +08:00
commit b9a3e97e84
168 changed files with 31794 additions and 0 deletions
+292
View File
@@ -0,0 +1,292 @@
package oci
import (
"context"
"fmt"
"strings"
"time"
"github.com/oracle/oci-go-sdk/v65/core"
)
// BootVolume 是一个引导卷;引导卷由创建实例产生,本面板不单独创建。
// AttachedInstanceID / AttachedInstanceName 与 ImageName 在列表与详情查询时都会补全。
type BootVolume struct {
ID string `json:"id"`
DisplayName string `json:"displayName"`
LifecycleState string `json:"lifecycleState"`
AvailabilityDomain string `json:"availabilityDomain"`
CompartmentID string `json:"compartmentId"`
SizeInGBs int64 `json:"sizeInGBs"`
VpusPerGB int64 `json:"vpusPerGB"`
ImageID string `json:"imageId"`
ImageName string `json:"imageName,omitempty"`
AttachedInstanceID string `json:"attachedInstanceId,omitempty"`
AttachedInstanceName string `json:"attachedInstanceName,omitempty"`
TimeCreated *time.Time `json:"timeCreated"`
}
// UpdateBootVolumeInput 是更新引导卷的输入;零值字段不更新。
// SizeInGBs 只能扩容,VpusPerGB 调整卷性能(10 均衡 / 20 高性能)。
type UpdateBootVolumeInput struct {
Region string
DisplayName string
SizeInGBs int64
VpusPerGB int64
}
func (c *RealClient) blockstorageClient(cred Credentials, region string) (core.BlockstorageClient, error) {
bc, err := core.NewBlockstorageClientWithConfigurationProvider(provider(cred))
if err != nil {
return bc, fmt.Errorf("new blockstorage client: %w", err)
}
applyProxy(&bc.BaseClient, cred)
if region != "" {
bc.SetRegion(normalizeRegion(region))
}
return bc, nil
}
// ListBootVolumes 实现 ClientavailabilityDomain 为空时列出全区域引导卷。
func (c *RealClient) ListBootVolumes(ctx context.Context, cred Credentials, region, availabilityDomain string) ([]BootVolume, error) {
bc, err := c.blockstorageClient(cred, region)
if err != nil {
return nil, err
}
var volumes []BootVolume
var page *string
for {
req := core.ListBootVolumesRequest{CompartmentId: cred.EffectiveCompartment(), Page: page}
if availabilityDomain != "" {
req.AvailabilityDomain = &availabilityDomain
}
resp, err := bc.ListBootVolumes(ctx, req)
if err != nil {
return nil, fmt.Errorf("list boot volumes: %w", err)
}
for _, item := range resp.Items {
volumes = append(volumes, toBootVolume(item))
}
if resp.OpcNextPage == nil {
c.attachBootVolumeInstances(ctx, cred, region, volumes)
c.attachBootVolumeImages(ctx, cred, region, volumes)
return orEmpty(volumes), nil
}
page = resp.OpcNextPage
}
}
// attachBootVolumeImages 批量补全来源镜像名:按 imageId 去重后逐个查询,
// 镜像已删除或查询失败时保持为空。
func (c *RealClient) attachBootVolumeImages(ctx context.Context, cred Credentials, region string, volumes []BootVolume) {
names := map[string]string{}
for _, v := range volumes {
if v.ImageID != "" {
names[v.ImageID] = ""
}
}
for id := range names {
img, err := c.GetImage(ctx, cred, region, id)
if err != nil {
continue
}
names[id] = img.DisplayName
}
for i := range volumes {
volumes[i].ImageName = names[volumes[i].ImageID]
}
}
// attachBootVolumeInstances 批量补全挂载实例:按 AD + compartment 分组各拉一次
// 全量挂载记录(免逐卷查询),构建 bootVolumeId → instanceId 映射后回填。
func (c *RealClient) attachBootVolumeInstances(ctx context.Context, cred Credentials, region string, volumes []BootVolume) {
cc, err := c.computeClient(cred, region)
if err != nil {
return
}
type scope struct{ ad, comp string }
attached := map[string]string{}
seen := map[scope]bool{}
for _, v := range volumes {
s := scope{v.AvailabilityDomain, v.CompartmentID}
if seen[s] {
continue
}
seen[s] = true
collectBootVolumeAttachments(ctx, cc, s.ad, hostCompartment(cred, s.comp), attached)
collectVolumeAttachedBootVolumes(ctx, cc, s.ad, hostCompartment(cred, s.comp), attached)
}
names := instanceNames(ctx, cc, attached)
for i := range volumes {
id := attached[volumes[i].ID]
volumes[i].AttachedInstanceID = id
volumes[i].AttachedInstanceName = names[id]
}
}
// instanceNames 按去重后的实例 ID 批量查询显示名;查询失败的留空。
func instanceNames(ctx context.Context, cc core.ComputeClient, attached map[string]string) map[string]string {
names := map[string]string{}
for _, instID := range attached {
if instID == "" || names[instID] != "" {
continue
}
resp, err := cc.GetInstance(ctx, core.GetInstanceRequest{InstanceId: &instID})
if err != nil {
continue
}
names[instID] = deref(resp.DisplayName)
}
return names
}
// collectVolumeAttachedBootVolumes 补齐「引导卷作数据卷附加」的挂载:这类挂载
// 记录在块卷挂载列表(volumeId 为引导卷 OCID),引导卷挂载列表查不到。
func collectVolumeAttachedBootVolumes(ctx context.Context, cc core.ComputeClient, ad string, compartmentID *string, out map[string]string) {
var page *string
for {
resp, err := cc.ListVolumeAttachments(ctx, core.ListVolumeAttachmentsRequest{
AvailabilityDomain: &ad,
CompartmentId: compartmentID,
Page: page,
})
if err != nil {
return
}
for _, att := range resp.Items {
id := deref(att.GetVolumeId())
if att.GetLifecycleState() == core.VolumeAttachmentLifecycleStateAttached &&
strings.Contains(id, ".bootvolume.") {
out[id] = deref(att.GetInstanceId())
}
}
if resp.OpcNextPage == nil {
return
}
page = resp.OpcNextPage
}
}
// collectBootVolumeAttachments 把一个 AD + compartment 下 ATTACHED 状态的挂载写入映射。
func collectBootVolumeAttachments(ctx context.Context, cc core.ComputeClient, ad string, compartmentID *string, out map[string]string) {
var page *string
for {
resp, err := cc.ListBootVolumeAttachments(ctx, core.ListBootVolumeAttachmentsRequest{
AvailabilityDomain: &ad,
CompartmentId: compartmentID,
Page: page,
})
if err != nil {
return
}
for _, att := range resp.Items {
if att.LifecycleState == core.BootVolumeAttachmentLifecycleStateAttached {
out[deref(att.BootVolumeId)] = deref(att.InstanceId)
}
}
if resp.OpcNextPage == nil {
return
}
page = resp.OpcNextPage
}
}
// GetBootVolume 实现 Client:查询引导卷并补全挂载的实例。
func (c *RealClient) GetBootVolume(ctx context.Context, cred Credentials, region, bootVolumeID string) (BootVolume, error) {
bc, err := c.blockstorageClient(cred, region)
if err != nil {
return BootVolume{}, err
}
resp, err := bc.GetBootVolume(ctx, core.GetBootVolumeRequest{BootVolumeId: &bootVolumeID})
if err != nil {
return BootVolume{}, fmt.Errorf("get boot volume %s: %w", bootVolumeID, err)
}
volume := toBootVolume(resp.BootVolume)
volume.AttachedInstanceID = c.bootVolumeAttachedInstance(ctx, cred, region, volume)
if volume.ImageID != "" {
if img, err := c.GetImage(ctx, cred, region, volume.ImageID); err == nil {
volume.ImageName = img.DisplayName
}
}
return volume, nil
}
// bootVolumeAttachedInstance 查询引导卷当前挂载的实例;查询失败视为未挂载。
func (c *RealClient) bootVolumeAttachedInstance(ctx context.Context, cred Credentials, region string, volume BootVolume) string {
cc, err := c.computeClient(cred, region)
if err != nil {
return ""
}
// 挂载记录在引导卷所在 compartment,传租户根会漏掉子 compartment 的挂载
resp, err := cc.ListBootVolumeAttachments(ctx, core.ListBootVolumeAttachmentsRequest{
AvailabilityDomain: &volume.AvailabilityDomain,
CompartmentId: hostCompartment(cred, volume.CompartmentID),
BootVolumeId: &volume.ID,
})
if err != nil {
return ""
}
for _, att := range resp.Items {
if att.LifecycleState == core.BootVolumeAttachmentLifecycleStateAttached {
return deref(att.InstanceId)
}
}
return ""
}
// UpdateBootVolume 实现 Client。
func (c *RealClient) UpdateBootVolume(ctx context.Context, cred Credentials, bootVolumeID string, in UpdateBootVolumeInput) (BootVolume, error) {
bc, err := c.blockstorageClient(cred, in.Region)
if err != nil {
return BootVolume{}, err
}
details := core.UpdateBootVolumeDetails{}
if in.DisplayName != "" {
details.DisplayName = &in.DisplayName
}
if in.SizeInGBs > 0 {
details.SizeInGBs = &in.SizeInGBs
}
if in.VpusPerGB > 0 {
details.VpusPerGB = &in.VpusPerGB
}
resp, err := bc.UpdateBootVolume(ctx, core.UpdateBootVolumeRequest{
BootVolumeId: &bootVolumeID,
UpdateBootVolumeDetails: details,
})
if err != nil {
return BootVolume{}, fmt.Errorf("update boot volume %s: %w", bootVolumeID, err)
}
return toBootVolume(resp.BootVolume), nil
}
// DeleteBootVolume 实现 Client;仅未挂载(detached)的引导卷可删除。
func (c *RealClient) DeleteBootVolume(ctx context.Context, cred Credentials, region, bootVolumeID string) error {
bc, err := c.blockstorageClient(cred, region)
if err != nil {
return err
}
if _, err := bc.DeleteBootVolume(ctx, core.DeleteBootVolumeRequest{BootVolumeId: &bootVolumeID}); err != nil {
return fmt.Errorf("delete boot volume %s: %w", bootVolumeID, err)
}
return nil
}
func toBootVolume(v core.BootVolume) BootVolume {
out := BootVolume{
ID: deref(v.Id),
DisplayName: deref(v.DisplayName),
LifecycleState: string(v.LifecycleState),
AvailabilityDomain: deref(v.AvailabilityDomain),
CompartmentID: deref(v.CompartmentId),
ImageID: deref(v.ImageId),
TimeCreated: sdkTime(v.TimeCreated),
}
if v.SizeInGBs != nil {
out.SizeInGBs = *v.SizeInGBs
}
if v.VpusPerGB != nil {
out.VpusPerGB = *v.VpusPerGB
}
return out
}