Files
oci-portal/internal/oci/vcndelete.go
T

211 lines
8.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package oci
import (
"context"
"fmt"
"time"
"github.com/oracle/oci-go-sdk/v65/core"
)
// 本文件实现 VCN 的级联删除:OCI 要求 VCN 删除前先清空其子网、网关、
// 网络安全组(NSG)与非默认路由表 / 安全列表 / DHCP 选项,顺序参照控制台删除向导。
// 子网仍被 VNIC 占用(实例未终止)时 OCI 返回 409 IncorrectState
// 该错误经 api 层提炼后提示用户先终止实例,不在此做实例级清理。
// deleteVcnCascade 依次清理关联资源后删除 VCN。
func deleteVcnCascade(ctx context.Context, vn core.VirtualNetworkClient, vcnID string) error {
vcnResp, err := vn.GetVcn(ctx, core.GetVcnRequest{VcnId: &vcnID})
if err != nil {
return fmt.Errorf("get vcn %s: %w", vcnID, err)
}
comp := deref(vcnResp.CompartmentId)
if err := deleteVcnSubnets(ctx, vn, comp, vcnID); err != nil {
return err
}
if err := clearVcnRouteTables(ctx, vn, comp, vcnID, deref(vcnResp.DefaultRouteTableId)); err != nil {
return err
}
if err := deleteVcnGateways(ctx, vn, comp, vcnID); err != nil {
return err
}
if err := deleteVcnSecondaries(ctx, vn, comp, vcnID, vcnResp.Vcn); err != nil {
return err
}
if err := deleteVcnNsgs(ctx, vn, vcnID); err != nil {
return err
}
if _, err := vn.DeleteVcn(ctx, core.DeleteVcnRequest{VcnId: &vcnID}); err != nil {
return fmt.Errorf("delete vcn %s: %w", vcnID, err)
}
return nil
}
// deleteVcnSubnets 删除 VCN 下全部子网并等待终止完成。
func deleteVcnSubnets(ctx context.Context, vn core.VirtualNetworkClient, compartmentID, vcnID string) error {
resp, err := vn.ListSubnets(ctx, core.ListSubnetsRequest{CompartmentId: &compartmentID, VcnId: &vcnID})
if err != nil {
return fmt.Errorf("list subnets: %w", err)
}
for _, s := range resp.Items {
if s.LifecycleState == core.SubnetLifecycleStateTerminated ||
s.LifecycleState == core.SubnetLifecycleStateTerminating {
continue
}
if _, err := vn.DeleteSubnet(ctx, core.DeleteSubnetRequest{SubnetId: s.Id}); err != nil {
return fmt.Errorf("delete subnet %s: %w", deref(s.DisplayName), err)
}
}
return waitSubnetsGone(ctx, vn, compartmentID, vcnID)
}
// waitSubnetsGone 轮询等待 VCN 下子网全部终止,超时 60 秒。
func waitSubnetsGone(ctx context.Context, vn core.VirtualNetworkClient, compartmentID, vcnID string) error {
for i := 0; i < 30; i++ {
resp, err := vn.ListSubnets(ctx, core.ListSubnetsRequest{CompartmentId: &compartmentID, VcnId: &vcnID})
if err != nil {
return fmt.Errorf("wait subnets terminated: %w", err)
}
alive := 0
for _, s := range resp.Items {
if s.LifecycleState != core.SubnetLifecycleStateTerminated {
alive++
}
}
if alive == 0 {
return nil
}
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(2 * time.Second):
}
}
return fmt.Errorf("wait subnets terminated: timeout after 60s")
}
// clearVcnRouteTables 清空所有路由表规则(解除对网关的引用)并删除非默认路由表。
func clearVcnRouteTables(ctx context.Context, vn core.VirtualNetworkClient, compartmentID, vcnID, defaultRtID string) error {
resp, err := vn.ListRouteTables(ctx, core.ListRouteTablesRequest{CompartmentId: &compartmentID, VcnId: &vcnID})
if err != nil {
return fmt.Errorf("list route tables: %w", err)
}
for _, rt := range resp.Items {
if len(rt.RouteRules) > 0 {
if _, err := vn.UpdateRouteTable(ctx, core.UpdateRouteTableRequest{
RtId: rt.Id,
UpdateRouteTableDetails: core.UpdateRouteTableDetails{RouteRules: []core.RouteRule{}},
}); err != nil {
return fmt.Errorf("clear route table %s: %w", deref(rt.DisplayName), err)
}
}
if deref(rt.Id) != defaultRtID {
if _, err := vn.DeleteRouteTable(ctx, core.DeleteRouteTableRequest{RtId: rt.Id}); err != nil {
return fmt.Errorf("delete route table %s: %w", deref(rt.DisplayName), err)
}
}
}
return nil
}
// deleteVcnGateways 删除 VCN 下的互联网 / NAT / 服务网关。
func deleteVcnGateways(ctx context.Context, vn core.VirtualNetworkClient, compartmentID, vcnID string) error {
if err := deleteInternetGateways(ctx, vn, compartmentID, vcnID); err != nil {
return err
}
if err := deleteNatGateways(ctx, vn, compartmentID, vcnID); err != nil {
return err
}
return deleteServiceGateways(ctx, vn, compartmentID, vcnID)
}
func deleteInternetGateways(ctx context.Context, vn core.VirtualNetworkClient, compartmentID, vcnID string) error {
resp, err := vn.ListInternetGateways(ctx, core.ListInternetGatewaysRequest{CompartmentId: &compartmentID, VcnId: &vcnID})
if err != nil {
return fmt.Errorf("list internet gateways: %w", err)
}
for _, g := range resp.Items {
if _, err := vn.DeleteInternetGateway(ctx, core.DeleteInternetGatewayRequest{IgId: g.Id}); err != nil {
return fmt.Errorf("delete internet gateway %s: %w", deref(g.DisplayName), err)
}
}
return nil
}
func deleteNatGateways(ctx context.Context, vn core.VirtualNetworkClient, compartmentID, vcnID string) error {
resp, err := vn.ListNatGateways(ctx, core.ListNatGatewaysRequest{CompartmentId: &compartmentID, VcnId: &vcnID})
if err != nil {
return fmt.Errorf("list nat gateways: %w", err)
}
for _, g := range resp.Items {
if _, err := vn.DeleteNatGateway(ctx, core.DeleteNatGatewayRequest{NatGatewayId: g.Id}); err != nil {
return fmt.Errorf("delete nat gateway %s: %w", deref(g.DisplayName), err)
}
}
return nil
}
func deleteServiceGateways(ctx context.Context, vn core.VirtualNetworkClient, compartmentID, vcnID string) error {
resp, err := vn.ListServiceGateways(ctx, core.ListServiceGatewaysRequest{CompartmentId: &compartmentID, VcnId: &vcnID})
if err != nil {
return fmt.Errorf("list service gateways: %w", err)
}
for _, g := range resp.Items {
if _, err := vn.DeleteServiceGateway(ctx, core.DeleteServiceGatewayRequest{ServiceGatewayId: g.Id}); err != nil {
return fmt.Errorf("delete service gateway %s: %w", deref(g.DisplayName), err)
}
}
return nil
}
// deleteVcnSecondaries 删除非默认安全列表与非默认 DHCP 选项。
func deleteVcnSecondaries(ctx context.Context, vn core.VirtualNetworkClient, compartmentID, vcnID string, vcn core.Vcn) error {
slResp, err := vn.ListSecurityLists(ctx, core.ListSecurityListsRequest{CompartmentId: &compartmentID, VcnId: &vcnID})
if err != nil {
return fmt.Errorf("list security lists: %w", err)
}
for _, sl := range slResp.Items {
if deref(sl.Id) == deref(vcn.DefaultSecurityListId) {
continue
}
if _, err := vn.DeleteSecurityList(ctx, core.DeleteSecurityListRequest{SecurityListId: sl.Id}); err != nil {
return fmt.Errorf("delete security list %s: %w", deref(sl.DisplayName), err)
}
}
dhResp, err := vn.ListDhcpOptions(ctx, core.ListDhcpOptionsRequest{CompartmentId: &compartmentID, VcnId: &vcnID})
if err != nil {
return fmt.Errorf("list dhcp options: %w", err)
}
for _, d := range dhResp.Items {
if deref(d.Id) == deref(vcn.DefaultDhcpOptionsId) {
continue
}
if _, err := vn.DeleteDhcpOptions(ctx, core.DeleteDhcpOptionsRequest{DhcpId: d.Id}); err != nil {
return fmt.Errorf("delete dhcp options %s: %w", deref(d.DisplayName), err)
}
}
return nil
}
// deleteVcnNsgs 删除 VCN 下全部网络安全组:遗留 NSG 会使 DeleteVcn 报
// 409 associated with NSG。仅按 VcnId 过滤,跨 compartment 的 NSG 也能覆盖;
// 子网删除后 VNIC 已不存在,NSG 不再有关联,可直接删除。
func deleteVcnNsgs(ctx context.Context, vn core.VirtualNetworkClient, vcnID string) error {
resp, err := vn.ListNetworkSecurityGroups(ctx, core.ListNetworkSecurityGroupsRequest{VcnId: &vcnID})
if err != nil {
return fmt.Errorf("list network security groups: %w", err)
}
for _, g := range resp.Items {
if g.LifecycleState == core.NetworkSecurityGroupLifecycleStateTerminated ||
g.LifecycleState == core.NetworkSecurityGroupLifecycleStateTerminating {
continue
}
if _, err := vn.DeleteNetworkSecurityGroup(ctx, core.DeleteNetworkSecurityGroupRequest{
NetworkSecurityGroupId: g.Id,
}); err != nil {
return fmt.Errorf("delete network security group %s: %w", deref(g.DisplayName), err)
}
}
return nil
}