初始提交:OCI 面板后端(含 GenAI 网关一期)
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
package oci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/oracle/oci-go-sdk/v65/core"
|
||||
)
|
||||
|
||||
// TestRealOCIImages 用试用期 key 验证镜像列表查询(只读)。
|
||||
func TestRealOCIImages(t *testing.T) {
|
||||
if os.Getenv("OCI_INTEGRATION_TEST") != "1" {
|
||||
t.Skip("set OCI_INTEGRATION_TEST=1 to run real OCI tests")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
cred := loadTestCredentials(t, "试用期")
|
||||
client := NewClient()
|
||||
|
||||
images, err := client.ListImages(ctx, cred, ImagesQuery{
|
||||
OperatingSystem: "Canonical Ubuntu",
|
||||
Shape: "VM.Standard.A1.Flex",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ListImages: %v", err)
|
||||
}
|
||||
if len(images) == 0 {
|
||||
t.Fatal("ListImages returned empty list for Ubuntu on A1")
|
||||
}
|
||||
for _, img := range images[:min(3, len(images))] {
|
||||
t.Logf("image %s os=%s %s size=%dMB", img.DisplayName, img.OperatingSystem, img.OperatingSystemVersion, img.SizeInMBs)
|
||||
if img.ID == "" || img.OperatingSystem == "" {
|
||||
t.Errorf("image %+v has empty mandatory field", img)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRealOCINetworkCRUDAndIPv6 走全链路:建 VCN → 建子网 → 建安全列表 →
|
||||
// 一键 IPv6 → 校验,结束后尽力清理全部资源。
|
||||
// 会创建真实云资源,资源名前缀 oci-portal-e2e-<timestamp>。
|
||||
func TestRealOCINetworkCRUDAndIPv6(t *testing.T) {
|
||||
if os.Getenv("OCI_INTEGRATION_TEST") != "1" {
|
||||
t.Skip("set OCI_INTEGRATION_TEST=1 to run real OCI tests")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Minute)
|
||||
defer cancel()
|
||||
cred := loadTestCredentials(t, "试用期")
|
||||
client := NewClient()
|
||||
prefix := fmt.Sprintf("oci-portal-e2e-%d", time.Now().Unix())
|
||||
|
||||
vcn, err := client.CreateVCN(ctx, cred, CreateVCNInput{
|
||||
DisplayName: prefix + "-vcn",
|
||||
CidrBlock: "10.99.0.0/16",
|
||||
DnsLabel: "ociportale2e",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("CreateVCN: %v", err)
|
||||
}
|
||||
t.Logf("created vcn %s (%s)", vcn.DisplayName, vcn.ID)
|
||||
defer cleanupVCN(t, client, cred, vcn.ID)
|
||||
|
||||
if _, err := client.UpdateVCN(ctx, cred, "", vcn.ID, prefix+"-vcn-renamed"); err != nil {
|
||||
t.Errorf("UpdateVCN: %v", err)
|
||||
}
|
||||
|
||||
subnet, err := client.CreateSubnet(ctx, cred, CreateSubnetInput{
|
||||
VcnID: vcn.ID,
|
||||
DisplayName: prefix + "-subnet",
|
||||
CidrBlock: "10.99.1.0/24",
|
||||
DnsLabel: "e2esub",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("CreateSubnet: %v", err)
|
||||
}
|
||||
t.Logf("created subnet %s (%s)", subnet.DisplayName, subnet.ID)
|
||||
|
||||
secList, err := client.CreateSecurityList(ctx, cred, CreateSecurityListInput{
|
||||
VcnID: vcn.ID,
|
||||
DisplayName: prefix + "-seclist",
|
||||
IngressRules: []SecurityRule{
|
||||
{Protocol: "6", Source: "0.0.0.0/0", PortMin: intPtr(22), PortMax: intPtr(22), Description: "ssh"},
|
||||
},
|
||||
EgressRules: []SecurityRule{{Protocol: "all", Destination: "0.0.0.0/0"}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("CreateSecurityList: %v", err)
|
||||
}
|
||||
t.Logf("created security list %s (%s)", secList.DisplayName, secList.ID)
|
||||
if len(secList.IngressRules) != 1 || secList.IngressRules[0].PortMin == nil || *secList.IngressRules[0].PortMin != 22 {
|
||||
t.Errorf("ingress rules = %+v, want one ssh rule with port 22", secList.IngressRules)
|
||||
}
|
||||
|
||||
updated, err := client.UpdateSecurityList(ctx, cred, secList.ID, UpdateSecurityListInput{
|
||||
IngressRules: &[]SecurityRule{
|
||||
{Protocol: "6", Source: "0.0.0.0/0", PortMin: intPtr(22), PortMax: intPtr(22), Description: "ssh"},
|
||||
{Protocol: "6", Source: "0.0.0.0/0", PortMin: intPtr(443), PortMax: intPtr(443), Description: "https"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateSecurityList: %v", err)
|
||||
}
|
||||
if len(updated.IngressRules) != 2 {
|
||||
t.Errorf("ingress rules after update = %d, want 2", len(updated.IngressRules))
|
||||
}
|
||||
|
||||
steps, err := client.EnableVCNIPv6(ctx, cred, "", vcn.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("EnableVCNIPv6: %v", err)
|
||||
}
|
||||
for _, s := range steps {
|
||||
t.Logf("ipv6 step=%s status=%s %s", s.Step, s.Status, s.Detail)
|
||||
if s.Status == "failed" {
|
||||
t.Errorf("ipv6 step %s failed: %s", s.Step, s.Detail)
|
||||
}
|
||||
}
|
||||
vcnAfter, err := client.GetVCN(ctx, cred, "", vcn.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetVCN after ipv6: %v", err)
|
||||
}
|
||||
if len(vcnAfter.Ipv6CidrBlocks) == 0 {
|
||||
t.Error("vcn has no ipv6 cidr after enable-ipv6")
|
||||
}
|
||||
subnetAfter, err := client.GetSubnet(ctx, cred, "", subnet.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetSubnet after ipv6: %v", err)
|
||||
}
|
||||
if subnetAfter.Ipv6CidrBlock == "" {
|
||||
t.Error("subnet has no ipv6 cidr after enable-ipv6")
|
||||
}
|
||||
t.Logf("vcn ipv6=%v subnet ipv6=%s", vcnAfter.Ipv6CidrBlocks, subnetAfter.Ipv6CidrBlock)
|
||||
|
||||
// 幂等:重复执行应全部 skipped
|
||||
steps2, err := client.EnableVCNIPv6(ctx, cred, "", vcn.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("EnableVCNIPv6 twice: %v", err)
|
||||
}
|
||||
for _, s := range steps2 {
|
||||
if s.Status != "skipped" {
|
||||
t.Errorf("rerun step %s status = %s, want skipped", s.Step, s.Status)
|
||||
}
|
||||
}
|
||||
|
||||
if err := client.DeleteSecurityList(ctx, cred, "", secList.ID); err != nil {
|
||||
t.Errorf("DeleteSecurityList: %v", err)
|
||||
}
|
||||
if err := client.DeleteSubnet(ctx, cred, "", subnet.ID); err != nil {
|
||||
t.Errorf("DeleteSubnet: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// cleanupVCN 尽力清理 VCN 及其关联资源:清空默认路由表、删 IGW、
|
||||
// 删残留子网与自建安全列表,最后删 VCN。
|
||||
func cleanupVCN(t *testing.T, client *RealClient, cred Credentials, vcnID string) {
|
||||
t.Helper()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
|
||||
defer cancel()
|
||||
vn, err := client.vcnClient(cred, "")
|
||||
if err != nil {
|
||||
t.Errorf("cleanup: vcn client: %v", err)
|
||||
return
|
||||
}
|
||||
vcnResp, err := vn.GetVcn(ctx, core.GetVcnRequest{VcnId: &vcnID})
|
||||
if err != nil {
|
||||
t.Errorf("cleanup: get vcn: %v", err)
|
||||
return
|
||||
}
|
||||
if _, err := vn.UpdateRouteTable(ctx, core.UpdateRouteTableRequest{
|
||||
RtId: vcnResp.DefaultRouteTableId,
|
||||
UpdateRouteTableDetails: core.UpdateRouteTableDetails{RouteRules: []core.RouteRule{}},
|
||||
}); err != nil {
|
||||
t.Logf("cleanup: clear route table: %v", err)
|
||||
}
|
||||
igws, err := vn.ListInternetGateways(ctx, core.ListInternetGatewaysRequest{
|
||||
CompartmentId: &cred.TenancyOCID, VcnId: &vcnID,
|
||||
})
|
||||
if err == nil {
|
||||
for _, igw := range igws.Items {
|
||||
if _, err := vn.DeleteInternetGateway(ctx, core.DeleteInternetGatewayRequest{IgId: igw.Id}); err != nil {
|
||||
t.Logf("cleanup: delete igw: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if subnets, err := listSubnets(ctx, vn, cred.TenancyOCID, vcnID); err == nil {
|
||||
for _, s := range subnets {
|
||||
if err := deleteSubnetWithRetry(ctx, client, cred, s.ID); err != nil {
|
||||
t.Logf("cleanup: delete subnet %s: %v", s.DisplayName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if lists, err := client.ListSecurityLists(ctx, cred, "", vcnID); err == nil {
|
||||
for _, sl := range lists {
|
||||
// 默认安全列表的名字包含 VCN 名,须用前缀匹配排除,它随 VCN 一起删除
|
||||
if strings.HasPrefix(sl.DisplayName, "oci-portal-e2e-") {
|
||||
if err := client.DeleteSecurityList(ctx, cred, "", sl.ID); err != nil {
|
||||
t.Logf("cleanup: delete security list %s: %v", sl.DisplayName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := client.DeleteVCN(ctx, cred, "", vcnID); err != nil {
|
||||
t.Errorf("cleanup: delete vcn: %v (manual cleanup may be required)", err)
|
||||
return
|
||||
}
|
||||
t.Logf("cleanup: vcn %s deleted", vcnID)
|
||||
}
|
||||
|
||||
// deleteSubnetWithRetry 删除子网;实例终止后 VNIC 异步释放,子网短暂
|
||||
// 保持被引用状态,409 Conflict 时等待重试,最长约 60 秒。
|
||||
func deleteSubnetWithRetry(ctx context.Context, client *RealClient, cred Credentials, subnetID string) error {
|
||||
var err error
|
||||
for i := 0; i < 12; i++ {
|
||||
if err = client.DeleteSubnet(ctx, cred, "", subnetID); err == nil {
|
||||
return nil
|
||||
}
|
||||
if !strings.Contains(err.Error(), "Conflict") {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return err
|
||||
case <-time.After(5 * time.Second):
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func intPtr(n int) *int { return &n }
|
||||
Reference in New Issue
Block a user