package oci import ( "context" "fmt" "time" "github.com/oracle/oci-go-sdk/v65/common" "github.com/oracle/oci-go-sdk/v65/core" ) // shapeCacheTTL 是 shape 清单缓存时长:区域提供的 shape 极少变化。 const shapeCacheTTL = 6 * time.Hour // ComputeShape 是租户在目标区域实际可用的一种实例规格。 // Flex 规格带 Ocpus/Memory 的可选范围;固定规格带其固定值。 type ComputeShape struct { Name string `json:"name"` BillingType string `json:"billingType"` // ALWAYS_FREE / LIMITED_FREE / PAID IsFlexible bool `json:"isFlexible"` Ocpus float32 `json:"ocpus,omitempty"` MemoryInGBs float32 `json:"memoryInGBs,omitempty"` OcpusMin float32 `json:"ocpusMin,omitempty"` OcpusMax float32 `json:"ocpusMax,omitempty"` MemoryMinGBs float32 `json:"memoryMinGBs,omitempty"` MemoryMaxGBs float32 `json:"memoryMaxGBs,omitempty"` Gpus int `json:"gpus,omitempty"` ProcessorDescription string `json:"processorDescription,omitempty"` // QuotaNames 是该 shape 对应的配额名(与 Limits 服务 compute limit name 同名), // 前端据此结合 limits 行判定配额与 AD 可用性。 QuotaNames []string `json:"quotaNames,omitempty"` } // shapeCacheEntry 是一份 shape 清单的缓存条目。 type shapeCacheEntry struct { expires time.Time shapes []ComputeShape } // ListShapes 实现 Client:列出租户在目标区域可用的实例规格(带 TTL 缓存)。 // availabilityDomain 为空时返回区域级去重清单。 func (c *RealClient) ListShapes(ctx context.Context, cred Credentials, region, availabilityDomain string) ([]ComputeShape, error) { key := cred.TenancyOCID + "|" + region + "|" + availabilityDomain if v, ok := c.shapes.Load(key); ok { if e := v.(shapeCacheEntry); time.Now().Before(e.expires) { return e.shapes, nil } } shapes, err := c.fetchShapes(ctx, cred, region, availabilityDomain) if err != nil { return nil, err } c.shapes.Store(key, shapeCacheEntry{expires: time.Now().Add(shapeCacheTTL), shapes: shapes}) return shapes, nil } // fetchShapes 分页拉取 shape 清单并按名称去重(不带 AD 时 OCI 每个可用域各返回一份)。 func (c *RealClient) fetchShapes(ctx context.Context, cred Credentials, region, availabilityDomain string) ([]ComputeShape, error) { cc, err := c.computeClient(cred, region) if err != nil { return nil, err } shapes := []ComputeShape{} seen := map[string]bool{} var page *string for { req := core.ListShapesRequest{ CompartmentId: cred.EffectiveCompartment(), Limit: common.Int(500), Page: page, } if availabilityDomain != "" { req.AvailabilityDomain = &availabilityDomain } resp, err := cc.ListShapes(ctx, req) if err != nil { return nil, fmt.Errorf("list shapes: %w", err) } for _, item := range resp.Items { if name := deref(item.Shape); !seen[name] { seen[name] = true shapes = append(shapes, toComputeShape(item)) } } if resp.OpcNextPage == nil { return shapes, nil } page = resp.OpcNextPage } } func toComputeShape(s core.Shape) ComputeShape { out := ComputeShape{ Name: deref(s.Shape), BillingType: string(s.BillingType), IsFlexible: s.OcpuOptions != nil, ProcessorDescription: deref(s.ProcessorDescription), QuotaNames: s.QuotaNames, } out.Ocpus, out.MemoryInGBs = deref32(s.Ocpus), deref32(s.MemoryInGBs) if s.Gpus != nil { out.Gpus = *s.Gpus } if o := s.OcpuOptions; o != nil { out.OcpusMin, out.OcpusMax = deref32(o.Min), deref32(o.Max) } if m := s.MemoryOptions; m != nil { out.MemoryMinGBs, out.MemoryMaxGBs = deref32(m.MinInGBs), deref32(m.MaxInGBs) } return out } // deref32 解引用 *float32,nil 时返回零值。 func deref32(p *float32) float32 { if p == nil { return 0 } return *p }