@@ -0,0 +1,150 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"oci-portal/internal/oci"
|
||||
)
|
||||
|
||||
type reservedIPStubClient struct {
|
||||
*fakeClient
|
||||
|
||||
mu sync.Mutex
|
||||
ips []oci.ReservedIP
|
||||
assigned map[string]string // publicIpId -> instanceId
|
||||
vnicBound map[string]string // publicIpId -> vnicId
|
||||
deleted []string
|
||||
instState string // GetInstance 返回的状态
|
||||
launched oci.CreateInstanceInput
|
||||
}
|
||||
|
||||
func (f *reservedIPStubClient) ListReservedIPs(ctx context.Context, cred oci.Credentials, region, compartmentID string) ([]oci.ReservedIP, error) {
|
||||
return f.ips, nil
|
||||
}
|
||||
|
||||
func (f *reservedIPStubClient) CreateReservedIP(ctx context.Context, cred oci.Credentials, region, compartmentID, displayName string) (oci.ReservedIP, error) {
|
||||
ip := oci.ReservedIP{ID: "ocid1.publicip..new", DisplayName: displayName, IPAddress: "155.248.0.10", LifecycleState: "AVAILABLE"}
|
||||
f.ips = append(f.ips, ip)
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func (f *reservedIPStubClient) AssignReservedIP(ctx context.Context, cred oci.Credentials, region, publicIPID, instanceID string) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.assigned == nil {
|
||||
f.assigned = map[string]string{}
|
||||
}
|
||||
f.assigned[publicIPID] = instanceID
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *reservedIPStubClient) AssignReservedIPToVnic(ctx context.Context, cred oci.Credentials, region, publicIPID, vnicID string) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.vnicBound == nil {
|
||||
f.vnicBound = map[string]string{}
|
||||
}
|
||||
f.vnicBound[publicIPID] = vnicID
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *reservedIPStubClient) DeleteReservedIP(ctx context.Context, cred oci.Credentials, region, publicIPID string) error {
|
||||
f.deleted = append(f.deleted, publicIPID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *reservedIPStubClient) LaunchInstance(ctx context.Context, cred oci.Credentials, in oci.CreateInstanceInput) (oci.Instance, error) {
|
||||
f.launched = in
|
||||
return oci.Instance{ID: "ocid1.instance..new", LifecycleState: "PROVISIONING"}, nil
|
||||
}
|
||||
|
||||
func (f *reservedIPStubClient) GetInstance(ctx context.Context, cred oci.Credentials, region, instanceID string) (oci.Instance, error) {
|
||||
return oci.Instance{ID: instanceID, LifecycleState: f.instState}, nil
|
||||
}
|
||||
|
||||
func (f *reservedIPStubClient) assignedTo(publicIPID string) string {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return f.assigned[publicIPID]
|
||||
}
|
||||
|
||||
func TestReservedIPCrud(t *testing.T) {
|
||||
client := &reservedIPStubClient{fakeClient: &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}}}
|
||||
svc := newTestService(t, client)
|
||||
cfg := importAliveConfig(t, svc)
|
||||
ctx := context.Background()
|
||||
|
||||
if _, err := svc.CreateReservedIP(ctx, cfg.ID, "ap-tokyo-1", "", "ip-1"); err != nil {
|
||||
t.Fatalf("CreateReservedIP: %v", err)
|
||||
}
|
||||
got, err := svc.ReservedIPs(ctx, cfg.ID, "ap-tokyo-1", "")
|
||||
if err != nil || len(got) != 1 || got[0].DisplayName != "ip-1" {
|
||||
t.Fatalf("ReservedIPs = %+v, %v", got, err)
|
||||
}
|
||||
// publicIpId 缺失被拒
|
||||
if err := svc.AssignReservedIP(ctx, cfg.ID, "ap-tokyo-1", "", "inst", ""); err == nil ||
|
||||
!strings.Contains(err.Error(), "publicIpId") {
|
||||
t.Errorf("AssignReservedIP 空 id err = %v, want publicIpId required", err)
|
||||
}
|
||||
if err := svc.AssignReservedIP(ctx, cfg.ID, "ap-tokyo-1", "ocid1.publicip..new", "ocid1.instance..a", ""); err != nil {
|
||||
t.Fatalf("AssignReservedIP: %v", err)
|
||||
}
|
||||
if got := client.assignedTo("ocid1.publicip..new"); got != "ocid1.instance..a" {
|
||||
t.Errorf("assigned = %q, want instance a", got)
|
||||
}
|
||||
// vnicId 非空时路由到按网卡绑定
|
||||
if err := svc.AssignReservedIP(ctx, cfg.ID, "ap-tokyo-1", "ocid1.publicip..new", "", "ocid1.vnic..v1"); err != nil {
|
||||
t.Fatalf("AssignReservedIP vnic: %v", err)
|
||||
}
|
||||
if got := client.vnicBound["ocid1.publicip..new"]; got != "ocid1.vnic..v1" {
|
||||
t.Errorf("vnicBound = %q, want vnic v1", got)
|
||||
}
|
||||
if err := svc.DeleteReservedIP(ctx, cfg.ID, "ap-tokyo-1", "ocid1.publicip..new"); err != nil {
|
||||
t.Fatalf("DeleteReservedIP: %v", err)
|
||||
}
|
||||
if len(client.deleted) != 1 {
|
||||
t.Errorf("deleted = %v, want 1 entry", client.deleted)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateInstanceWithReservedIP(t *testing.T) {
|
||||
client := &reservedIPStubClient{
|
||||
fakeClient: &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}},
|
||||
instState: "RUNNING",
|
||||
}
|
||||
svc := newTestService(t, client)
|
||||
cfg := importAliveConfig(t, svc)
|
||||
ctx := context.Background()
|
||||
in := oci.CreateInstanceInput{
|
||||
AvailabilityDomain: "AD-1",
|
||||
DisplayName: "vm1",
|
||||
Shape: "VM.Standard.A1.Flex",
|
||||
ImageID: "ocid1.image..a",
|
||||
SubnetID: "ocid1.subnet..s",
|
||||
ReservedPublicIPID: "ocid1.publicip..r",
|
||||
}
|
||||
|
||||
// 批量创建 + 保留 IP 被拒
|
||||
if _, _, err := svc.CreateInstances(ctx, cfg.ID, in, 2); err == nil ||
|
||||
!strings.Contains(err.Error(), "single instance") {
|
||||
t.Fatalf("批量+保留IP err = %v, want single instance", err)
|
||||
}
|
||||
|
||||
instances, failures, err := svc.CreateInstances(ctx, cfg.ID, in, 1)
|
||||
if err != nil || len(failures) != 0 || len(instances) != 1 {
|
||||
t.Fatalf("CreateInstances = %+v, %v, %v", instances, failures, err)
|
||||
}
|
||||
// 后台 goroutine 轮询到 RUNNING 后完成绑定
|
||||
deadline := time.Now().Add(3 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
if client.assignedTo("ocid1.publicip..r") == "ocid1.instance..new" {
|
||||
return
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
t.Fatalf("assigned = %q, want bind to new instance", client.assignedTo("ocid1.publicip..r"))
|
||||
}
|
||||
Reference in New Issue
Block a user