Files
oci-portal/internal/service/reservedip_test.go
T

151 lines
5.1 KiB
Go

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"))
}