Skip to content

Network VirtualIPs

github-actions[bot] edited this page Oct 11, 2026 · 2 revisions

Network Virtual IPs

Public-IP price discovery and private virtual IP writes for danny.vn/vngcloud/network. See Network for security groups, VPCs, subnets, and Private DNS, and Services for the read-only virtual IP list and its address pairs.

This page assumes cfg, ctx, and client := network.New(cfg) from Network's Setup, and a subnet's subnetID from Creating, renaming, and deleting subnets.

Public-IP price discovery

ListNetworkPrices returns three separate account-scoped public-vip quotes for public, public-vm, and public-mkp. Each row uses IP-month. Equal amounts do not establish interchangeable products or a price for every public IP. Floating IP, external-interface, and reservation charges remain unknown rows with nil prices. Bandwidth tariffs and capacity units remain unverified and excluded.

prices, err := client.ListNetworkPrices(ctx, &network.ListNetworkPricesInput{
    MaxRequests: 4,
    AllowPartial: true,
})

Nil input uses defaults. MaxRequests defaults to 500 and accepts 1..2000. Discovery needs three logical quotes plus one project read when resolution is uncached. The full quote phase must fit before any quote starts. Requests run serially with at least 500 ms between starts, including retries, and a ten-minute child deadline. Each response is bounded to 8 MiB. Shared project or authentication waits can exceed the deadline; cancellation stops work after acquisition. Catalogs and quotes are cached only within the call, while existing project and token caches remain shared.

Each row has Kind, Name, Type, Scope, Unit, and Price. Quoted prices use the returned monthly total, VND, and tax as returned. Optional discounts retain field presence. Zero without explicit free-entry evidence is rejected. SDK rows follow stable type order, skipping absent fields. Metadata reports the configured region, logical request count, UTC completion time, coverage, failures, and sourced notes. Complete coverage can include unknown costs. Quotes reserve no price or capacity and place no order.

Without AllowPartial, a failure returns nil output and the original error. With it, independent quotes continue and failed rows keep nil prices. Output returns with *pricing.DiscoveryError, which matches pricing.ErrDiscoveryIncomplete and retains API and context errors. A failure before any usable quote returns nil output and its cause. Authentication, exhausted throttling, and cancellation stop remaining quotes.

vngcloud --region hcm-3 --read-only network list-network-prices
vngcloud --region hcm-3 --output json network list-network-prices --allow-partial
vngcloud --region hcm-3 network list-network-prices --sort-by monthly-price --descending

JSON contains Items and Metadata. Table and tab-separated text print nil amounts as unknown and notes on stderr. --sort-by accepts name or monthly-price; unit-price sorting is invalid. Numeric sorts use unrounded values and put unknown prices last in either direction. Sorting precedes --query. Partial output prints before the error and exits 1, even when a query hides metadata. See CLI Network for flags.

NAT costs remain excluded. Use network list-nat-zones, network list-nat-packages, and network quote-create-nat-instance separately, as described in Network NAT. Source notes distinguish published rules from account quotes; discovery reads no websites at runtime.

Creating, updating, and deleting virtual IPs

created, err := client.CreateVirtualIPAddress(ctx, &network.CreateVirtualIPAddressInput{
	SubnetID: subnetID,
	Name:     "lb-vip",
	Mode:     network.VirtualIPModeActivePassive,
})
if err != nil {
	log.Fatal(err)
}
log.Println(created.VirtualIPAddress.UUID)

updated, err := client.UpdateVirtualIPAddress(ctx, &network.UpdateVirtualIPAddressInput{
	VirtualIPAddressID: created.VirtualIPAddress.UUID,
	Description:        vngcloud.Ptr("front door failover pair"),
})
if err != nil {
	log.Fatal(err)
}
log.Println(updated.VirtualIPAddress.Description)

if _, err := client.DeleteVirtualIPAddress(ctx, &network.DeleteVirtualIPAddressInput{
	VirtualIPAddressID: created.VirtualIPAddress.UUID,
}); err != nil {
	log.Fatal(err)
}

Mode is required on every create; network.VirtualIPModeActiveActive ("Active/Active") and network.VirtualIPModeActivePassive ("Active/Passive") name the documented values, but the SDK sends Mode as given and does not check it against them, so a value the server adds later never needs an SDK release. IPAddress, when set, must parse with net/netip.ParseAddr as an IPv4 address; left empty, the server chooses it. Whether an address lies in the subnet, and whether it is already in use, are the server's own checks. CreateVirtualIPAddress never sends tags, zoneId, or a public type; a public virtual IP has its own create call, not covered here.

CreateVirtualIPAddress is a POST and is never retried after a failure that may already have reached the server: list virtual IPs with ListVirtualIPAddresses and match the name exactly, or the address if one was given, since the server keeps an address unique within a subnet, before creating it again. If the create response is not already "ACTIVE", it then waits; see Waits below.

UpdateVirtualIPAddress changes Name, Description, Mode, or any combination; at least one must be set, or the call fails with vngcloud.ErrInvalidInput and sends nothing. The API replaces every field on each PUT and requires Mode on every call, so the SDK reads the virtual IP first and resends whichever field the caller left nil unchanged. The PUT is marked idempotent. Whether the server actually lets Mode change on an existing virtual IP is not yet confirmed live.

DeleteVirtualIPAddress reads the virtual IP first and sends nothing when any address pair is still attached, checked both by the read's own AddressPairIPs and by ListAddressPairsByVirtualIPAddress (network.ErrInUse): a pair binds the address to a server interface, and deleting it would move traffic. It also sends nothing for a virtual IP whose Type is not the recorded lowercase "private" (vngcloud.ErrInvalidInput), so a public virtual IP, which has its own delete call and price, is never deleted through this one; an empty Type is refused the same way. DELETE is idempotent; a retry that finds the virtual IP already gone returns vngcloud.IsNotFound(err) == true. Deleting a subnet that still holds a virtual IP is refused; see Creating, renaming, and deleting subnets.

Waits

CreateVirtualIPAddress skips its wait entirely when the create response is already "ACTIVE". Otherwise, it polls GetVirtualIPAddress every 2 seconds for up to 60 seconds of elapsed time, tolerating a 404, until the virtual IP reaches "ACTIVE". If it reaches "ERROR" instead, the create returns an error wrapping network.ErrFailed; once the bound runs out, or a read or the wait's own sleep fails, it wraps network.ErrNotSettled instead, and the create must not be repeated. Either way the Output still holds the last virtual IP a read returned, or, if none did, the one the create response itself carried.

UpdateVirtualIPAddress takes no wait of its own: it sends the PUT, then always reads the virtual IP once more and returns that read as the Output. If that confirm read fails, the write has already succeeded: the error wraps network.ErrNotSettled, and the Output falls back to the PUT's own response instead. DeleteVirtualIPAddress takes no wait; the delete is treated as synchronous.

Errors

CreateVirtualIPAddress, UpdateVirtualIPAddress, and DeleteVirtualIPAddress share network.ErrInUse, network.ErrFailed, and network.ErrNotSettled with the rest of network's writes; see Network's Errors. They add no sentinel of their own.

Clone this wiki locally