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

187 lines
6.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package oci
import (
"context"
"fmt"
"time"
"github.com/oracle/oci-go-sdk/v65/common"
"github.com/oracle/oci-go-sdk/v65/core"
)
// instanceVnics 列出实例的全部 VNIC,并一并返回实例所在 compartment
// 供调用方复用为后续按实例定位查询的 compartmentId,避免重复 GetInstance。
func (c *RealClient) instanceVnics(ctx context.Context, cred Credentials, region, instanceID string) ([]core.Vnic, string, error) {
cc, err := c.computeClient(cred, region)
if err != nil {
return nil, "", err
}
vn, err := c.vcnClient(cred, region)
if err != nil {
return nil, "", err
}
// VNIC 挂载记录在实例所在 compartment,先取实例定位,传租户根会漏查
instResp, err := cc.GetInstance(ctx, core.GetInstanceRequest{InstanceId: &instanceID})
if err != nil {
return nil, "", fmt.Errorf("get instance %s: %w", instanceID, err)
}
compartmentID := hostCompartment(cred, deref(instResp.CompartmentId))
resp, err := cc.ListVnicAttachments(ctx, core.ListVnicAttachmentsRequest{
CompartmentId: compartmentID,
InstanceId: &instanceID,
})
if err != nil {
return nil, "", fmt.Errorf("list vnic attachments: %w", err)
}
return attachedVnics(ctx, vn, resp.Items), *compartmentID, nil
}
// attachedVnics 逐挂载记录查询 VNIC 详情,跳过查询失败的记录。
func attachedVnics(ctx context.Context, vn core.VirtualNetworkClient, atts []core.VnicAttachment) []core.Vnic {
vnics := make([]core.Vnic, 0, len(atts))
for _, att := range atts {
if att.VnicId == nil {
continue
}
vnicResp, err := vn.GetVnic(ctx, core.GetVnicRequest{VnicId: att.VnicId})
if err != nil {
continue
}
vnics = append(vnics, vnicResp.Vnic)
}
return vnics
}
// lookupPublicIP 查询私有 IP 绑定的公网 IP,未绑定返回 nil。
func lookupPublicIP(ctx context.Context, vn core.VirtualNetworkClient, privateIPID *string) *core.PublicIp {
resp, err := vn.GetPublicIpByPrivateIpId(ctx, core.GetPublicIpByPrivateIpIdRequest{
GetPublicIpByPrivateIpIdDetails: core.GetPublicIpByPrivateIpIdDetails{PrivateIpId: privateIPID},
})
if err != nil || resp.Id == nil {
return nil
}
return &resp.PublicIp
}
// ChangeInstancePublicIP 实现 Client:为实例主 VNIC 主私有 IP 更换临时公网 IP。
// 旧临时 IP 删除、旧保留 IP 自动解绑(资源保留在账户),随后分配新的临时 IP。
func (c *RealClient) ChangeInstancePublicIP(ctx context.Context, cred Credentials, region, instanceID string) (string, error) {
vn, err := c.vcnClient(cred, region)
if err != nil {
return "", err
}
priv, err := c.primaryPrivateIP(ctx, cred, region, instanceID)
if err != nil {
return "", err
}
return replaceEphemeralPublicIP(ctx, vn, cred, priv)
}
// ChangeVnicPublicIP 实现 Client:为指定 VNIC 的主私有 IP 更换临时公网 IP(次要网卡场景)。
func (c *RealClient) ChangeVnicPublicIP(ctx context.Context, cred Credentials, region, vnicID string) (string, error) {
vn, err := c.vcnClient(cred, region)
if err != nil {
return "", err
}
priv, err := vnicPrimaryPrivateIP(ctx, vn, vnicID)
if err != nil {
return "", err
}
return replaceEphemeralPublicIP(ctx, vn, cred, priv)
}
// replaceEphemeralPublicIP 释放私有 IP 上现有公网 IP(临时删除、保留解绑)并分配新临时 IP。
func replaceEphemeralPublicIP(ctx context.Context, vn core.VirtualNetworkClient, cred Credentials, priv core.PrivateIp) (string, error) {
if err := detachExistingPublicIP(ctx, vn, priv.Id); err != nil {
return "", err
}
// OCI 要求临时公网 IP 与其私有 IP 同 compartment,故不能用生效 compartment
resp, err := vn.CreatePublicIp(ctx, core.CreatePublicIpRequest{
CreatePublicIpDetails: core.CreatePublicIpDetails{
CompartmentId: hostCompartment(cred, deref(priv.CompartmentId)),
Lifetime: core.CreatePublicIpDetailsLifetimeEphemeral,
PrivateIpId: priv.Id,
},
})
if err != nil {
return "", fmt.Errorf("create public ip: %w", err)
}
return deref(resp.IpAddress), nil
}
// detachExistingPublicIP 释放私有 IP 上已绑定的公网 IP:
// 临时 IP 直接删除;保留 IP 仅解绑(回到未分配状态,资源不删除)。
func detachExistingPublicIP(ctx context.Context, vn core.VirtualNetworkClient, privateIPID *string) error {
existing := lookupPublicIP(ctx, vn, privateIPID)
if existing == nil {
return nil
}
if existing.Lifetime != core.PublicIpLifetimeReserved {
if _, err := vn.DeletePublicIp(ctx, core.DeletePublicIpRequest{PublicIpId: existing.Id}); err != nil {
return fmt.Errorf("delete old public ip: %w", err)
}
return nil
}
_, err := vn.UpdatePublicIp(ctx, core.UpdatePublicIpRequest{
PublicIpId: existing.Id,
UpdatePublicIpDetails: core.UpdatePublicIpDetails{PrivateIpId: common.String("")},
})
if err != nil {
return fmt.Errorf("unassign reserved public ip: %w", err)
}
waitPublicIPDetached(ctx, vn, privateIPID)
return nil
}
// waitPublicIPDetached 短轮询等私有 IP 上的旧绑定释放;保留 IP 解绑为异步操作,
// 立即创建新临时 IP 可能撞上未释放的旧绑定。超时不报错,交由后续创建操作反馈。
func waitPublicIPDetached(ctx context.Context, vn core.VirtualNetworkClient, privateIPID *string) {
for i := 0; i < 10; i++ {
if lookupPublicIP(ctx, vn, privateIPID) == nil {
return
}
select {
case <-ctx.Done():
return
case <-time.After(500 * time.Millisecond):
}
}
}
// primaryVnic 返回实例的主 VNIC。
func (c *RealClient) primaryVnic(ctx context.Context, cred Credentials, region, instanceID string) (core.Vnic, error) {
vnics, _, err := c.instanceVnics(ctx, cred, region, instanceID)
if err != nil {
return core.Vnic{}, err
}
for i := range vnics {
if vnics[i].IsPrimary != nil && *vnics[i].IsPrimary {
return vnics[i], nil
}
}
return core.Vnic{}, fmt.Errorf("instance %s has no primary vnic", instanceID)
}
// primaryPrivateIP 返回实例主 VNIC 上的主私有 IP 对象;
// 保留完整对象是为了让调用方能拿到私有 IP 所在的 compartment。
func (c *RealClient) primaryPrivateIP(ctx context.Context, cred Credentials, region, instanceID string) (core.PrivateIp, error) {
vn, err := c.vcnClient(cred, region)
if err != nil {
return core.PrivateIp{}, err
}
vnic, err := c.primaryVnic(ctx, cred, region, instanceID)
if err != nil {
return core.PrivateIp{}, err
}
privResp, err := vn.ListPrivateIps(ctx, core.ListPrivateIpsRequest{VnicId: vnic.Id})
if err != nil {
return core.PrivateIp{}, fmt.Errorf("list private ips: %w", err)
}
for _, priv := range privResp.Items {
if priv.IsPrimary != nil && *priv.IsPrimary {
return priv, nil
}
}
return core.PrivateIp{}, fmt.Errorf("change public ip: primary vnic has no primary private ip")
}