151 lines
5.1 KiB
Go
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"))
|
|
}
|