diff --git a/internal/oci/instance.go b/internal/oci/instance.go index e177390..691f9c8 100644 --- a/internal/oci/instance.go +++ b/internal/oci/instance.go @@ -397,9 +397,10 @@ func fillInstanceIPs(ctx context.Context, cc core.ComputeClient, vn core.Virtual return } var ( - wg sync.WaitGroup - mu sync.Mutex - sem = make(chan struct{}, 8) + wg sync.WaitGroup + mu sync.Mutex + sem = make(chan struct{}, 8) + primarySeen = make(map[*Instance]bool) ) for _, att := range attResp.Items { inst, ok := active[deref(att.InstanceId)] @@ -416,16 +417,30 @@ func fillInstanceIPs(ctx context.Context, cc core.ComputeClient, vn core.Virtual return } mu.Lock() - inst.SubnetID = deref(vnicResp.SubnetId) - inst.PrivateIP = deref(vnicResp.PrivateIp) - inst.PublicIP = deref(vnicResp.PublicIp) - inst.Ipv6Addresses = vnicResp.Ipv6Addresses + applyVnicAddrs(inst, vnicResp.Vnic, primarySeen) mu.Unlock() }(att.VnicId, inst) } wg.Wait() } +// applyVnicAddrs 将 VNIC 地址写入实例。多网卡实例以主网卡为准: +// 主网卡返回前先用先到的网卡兜底,主网卡到达后覆盖并锁定,避免并发 +// 完成顺序决定展示结果。调用方需持有保护 inst 与 primarySeen 的锁。 +func applyVnicAddrs(inst *Instance, v core.Vnic, primarySeen map[*Instance]bool) { + isPrimary := v.IsPrimary != nil && *v.IsPrimary + if !isPrimary && (primarySeen[inst] || inst.SubnetID != "") { + return + } + inst.SubnetID = deref(v.SubnetId) + inst.PrivateIP = deref(v.PrivateIp) + inst.PublicIP = deref(v.PublicIp) + inst.Ipv6Addresses = v.Ipv6Addresses + if isPrimary { + primarySeen[inst] = true + } +} + func toInstance(inst core.Instance) Instance { out := Instance{ ID: deref(inst.Id), diff --git a/internal/oci/instance_test.go b/internal/oci/instance_test.go index ce9a91b..41dcd9b 100644 --- a/internal/oci/instance_test.go +++ b/internal/oci/instance_test.go @@ -5,9 +5,51 @@ import ( "strings" "testing" + "github.com/oracle/oci-go-sdk/v65/common" "github.com/oracle/oci-go-sdk/v65/core" ) +func TestApplyVnicAddrs(t *testing.T) { + primary := core.Vnic{ + IsPrimary: common.Bool(true), + SubnetId: common.String("sub-a"), + PrivateIp: common.String("10.0.0.2"), + PublicIp: common.String("1.1.1.1"), + } + secondary := core.Vnic{ + IsPrimary: common.Bool(false), + SubnetId: common.String("sub-b"), + PrivateIp: common.String("10.0.0.9"), + } + secondary2 := core.Vnic{ + SubnetId: common.String("sub-c"), + PrivateIp: common.String("10.0.0.7"), + } + tests := []struct { + name string + order []core.Vnic + wantPrivate string + wantSubnet string + }{ + {name: "主卡先到不被次卡覆盖", order: []core.Vnic{primary, secondary}, wantPrivate: "10.0.0.2", wantSubnet: "sub-a"}, + {name: "次卡先兜底主卡后覆盖", order: []core.Vnic{secondary, primary}, wantPrivate: "10.0.0.2", wantSubnet: "sub-a"}, + {name: "无主卡时先到者保留", order: []core.Vnic{secondary, secondary2}, wantPrivate: "10.0.0.9", wantSubnet: "sub-b"}, + {name: "仅次卡也能兜底", order: []core.Vnic{secondary2}, wantPrivate: "10.0.0.7", wantSubnet: "sub-c"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + inst := &Instance{} + seen := make(map[*Instance]bool) + for _, v := range tt.order { + applyVnicAddrs(inst, v, seen) + } + if inst.PrivateIP != tt.wantPrivate || inst.SubnetID != tt.wantSubnet { + t.Errorf("got (%q, %q), want (%q, %q)", inst.PrivateIP, inst.SubnetID, tt.wantPrivate, tt.wantSubnet) + } + }) + } +} + func TestShellSingleQuote(t *testing.T) { tests := []struct { name string