Files
oci-portal/internal/oci/vnic.go
T

163 lines
5.4 KiB
Go

package oci
import (
"context"
"fmt"
"time"
"github.com/oracle/oci-go-sdk/v65/core"
)
// Vnic 是实例已附加的虚拟网卡(附加关系与地址信息的合并视图)。
type Vnic struct {
AttachmentID string `json:"attachmentId"`
VnicID string `json:"vnicId"`
DisplayName string `json:"displayName"`
IsPrimary bool `json:"isPrimary"`
PrivateIP string `json:"privateIp"`
PublicIP string `json:"publicIp"`
Ipv6Addresses []string `json:"ipv6Addresses"`
SubnetID string `json:"subnetId"`
SubnetName string `json:"subnetName,omitempty"`
LifecycleState string `json:"lifecycleState"`
TimeCreated *time.Time `json:"timeCreated"`
}
// AttachVnicInput 是附加新 VNIC 的输入;PrivateIP 留空自动分配。
type AttachVnicInput struct {
SubnetID string `json:"subnetId" binding:"required"`
DisplayName string `json:"displayName"`
PrivateIP string `json:"privateIp"`
AssignPublicIP bool `json:"assignPublicIp"`
// AssignIpv6 为 true 时同时自动分配一个 IPv6(要求子网已启用 IPv6)。
AssignIpv6 bool `json:"assignIpv6"`
// ReservedPublicIPID 非空时不分配临时公网 IP,VNIC 就绪后由后台绑定该保留 IP。
ReservedPublicIPID string `json:"reservedPublicIpId"`
}
// ListInstanceVnics 实现 Client:列出实例全部 VNIC(含附加中/分离中的关系)。
func (c *RealClient) ListInstanceVnics(ctx context.Context, cred Credentials, region, instanceID string) ([]Vnic, error) {
cc, err := c.computeClient(cred, region)
if err != nil {
return nil, err
}
instResp, err := cc.GetInstance(ctx, core.GetInstanceRequest{InstanceId: &instanceID})
if err != nil {
return nil, fmt.Errorf("get instance %s: %w", instanceID, err)
}
// 附加关系在实例所在 compartment,传租户根会漏查
attResp, err := cc.ListVnicAttachments(ctx, core.ListVnicAttachmentsRequest{
CompartmentId: hostCompartment(cred, deref(instResp.CompartmentId)),
InstanceId: &instanceID,
})
if err != nil {
return nil, fmt.Errorf("list vnic attachments: %w", err)
}
vn, err := c.vcnClient(cred, region)
if err != nil {
return nil, err
}
out := make([]Vnic, 0, len(attResp.Items))
subnetNames := map[string]string{}
for _, att := range attResp.Items {
out = append(out, buildVnic(ctx, vn, att, subnetNames))
}
return out, nil
}
// buildVnic 合并附加关系与 VNIC 详情;VNIC 未就绪(附加中)时只有关系字段。
func buildVnic(ctx context.Context, vn core.VirtualNetworkClient, att core.VnicAttachment, subnetNames map[string]string) Vnic {
v := Vnic{
AttachmentID: deref(att.Id),
VnicID: deref(att.VnicId),
DisplayName: deref(att.DisplayName),
SubnetID: deref(att.SubnetId),
LifecycleState: string(att.LifecycleState),
TimeCreated: sdkTime(att.TimeCreated),
}
if att.VnicId != nil {
if resp, err := vn.GetVnic(ctx, core.GetVnicRequest{VnicId: att.VnicId}); err == nil {
v.DisplayName = deref(resp.DisplayName)
v.PrivateIP = deref(resp.PrivateIp)
v.PublicIP = deref(resp.PublicIp)
v.Ipv6Addresses = resp.Ipv6Addresses
if resp.IsPrimary != nil {
v.IsPrimary = *resp.IsPrimary
}
if deref(resp.SubnetId) != "" {
v.SubnetID = deref(resp.SubnetId)
}
}
}
v.SubnetName = subnetNameOf(ctx, vn, v.SubnetID, subnetNames)
return v
}
// subnetNameOf 查子网显示名,map 去重;失败留空不影响列表。
func subnetNameOf(ctx context.Context, vn core.VirtualNetworkClient, id string, cache map[string]string) string {
if id == "" {
return ""
}
if name, ok := cache[id]; ok {
return name
}
name := ""
if resp, err := vn.GetSubnet(ctx, core.GetSubnetRequest{SubnetId: &id}); err == nil {
name = deref(resp.DisplayName)
}
cache[id] = name
return name
}
// AttachVnic 实现 Client:为实例附加次要 VNIC;实例须运行中且 shape 有空余 VNIC 配额。
func (c *RealClient) AttachVnic(ctx context.Context, cred Credentials, region, instanceID string, in AttachVnicInput) (Vnic, error) {
cc, err := c.computeClient(cred, region)
if err != nil {
return Vnic{}, err
}
// 选了保留 IP 就不分配临时公网 IP,避免绑定时还要先删一次
if in.ReservedPublicIPID != "" {
in.AssignPublicIP = false
}
details := &core.CreateVnicDetails{
SubnetId: &in.SubnetID,
AssignPublicIp: &in.AssignPublicIP,
}
if in.AssignIpv6 {
details.AssignIpv6Ip = &in.AssignIpv6
}
if in.DisplayName != "" {
details.DisplayName = &in.DisplayName
}
if in.PrivateIP != "" {
details.PrivateIp = &in.PrivateIP
}
resp, err := cc.AttachVnic(ctx, core.AttachVnicRequest{
AttachVnicDetails: core.AttachVnicDetails{InstanceId: &instanceID, CreateVnicDetails: details},
})
if err != nil {
return Vnic{}, fmt.Errorf("attach vnic: %w", err)
}
att := resp.VnicAttachment
return Vnic{
AttachmentID: deref(att.Id),
VnicID: deref(att.VnicId),
DisplayName: deref(att.DisplayName),
SubnetID: deref(att.SubnetId),
LifecycleState: string(att.LifecycleState),
TimeCreated: sdkTime(att.TimeCreated),
}, nil
}
// DetachVnic 实现 Client:分离次要 VNIC(主 VNIC 由 OCI 拒绝)。
func (c *RealClient) DetachVnic(ctx context.Context, cred Credentials, region, attachmentID string) error {
cc, err := c.computeClient(cred, region)
if err != nil {
return err
}
if _, err := cc.DetachVnic(ctx, core.DetachVnicRequest{VnicAttachmentId: &attachmentID}); err != nil {
return fmt.Errorf("detach vnic %s: %w", attachmentID, err)
}
return nil
}