初始提交:OCI 面板后端(含 GenAI 网关一期)
This commit is contained in:
@@ -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 实现 Client;availabilityDomain 为空时列出全区域引导卷。
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user