Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Documentation/cmdref/cilium-operator-azure.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Documentation/cmdref/cilium-operator.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions operator/cmd/provider_azure_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,11 @@ func (hook *azureFlagsHooks) RegisterProviderFlag(cmd *cobra.Command, vp *viper.
flags.Bool(operatorOption.AzureUsePrimaryAddress, false, "Use Azure IP address from interface's primary IPConfigurations")
option.BindEnvWithLegacyEnvFallback(vp, operatorOption.AzureUsePrimaryAddress, "AZURE_USE_PRIMARY_ADDRESS")

flags.Bool(operatorOption.AzureReleaseExcessIPs, false, "Enable releasing excess free IP addresses from Azure NICs.")
option.BindEnvWithLegacyEnvFallback(vp, operatorOption.AzureReleaseExcessIPs, "AZURE_RELEASE_EXCESS_IPS")

flags.Int(operatorOption.ExcessIPReleaseDelay, 180, "Number of seconds operator would wait before it releases an IP previously marked as excess")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

option.BindEnv(vp, operatorOption.ExcessIPReleaseDelay)

vp.BindPFlags(flags)
}
9 changes: 9 additions & 0 deletions operator/option/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ const (
// primary IPConfiguration
AzureUsePrimaryAddress = "azure-use-primary-address"

// AzureReleaseExcessIPs allows releasing excess free IP addresses from Azure NICs.
// Enabling this option reduces waste of IP addresses but may increase
// the number of API calls to Azure.
AzureReleaseExcessIPs = "azure-release-excess-ips"

// LeaderElectionLeaseDuration is the duration that non-leader candidates will wait to
// force acquire leadership
LeaderElectionLeaseDuration = "leader-election-lease-duration"
Expand Down Expand Up @@ -349,6 +354,9 @@ type OperatorConfig struct {
// primary IPConfiguration
AzureUsePrimaryAddress bool

// AzureReleaseExcessIPs allows releasing excess free IP addresses from Azure NICs
AzureReleaseExcessIPs bool

// AlibabaCloud options

// AlibabaCloudVPCID allow user to specific vpc
Expand Down Expand Up @@ -466,6 +474,7 @@ func (c *OperatorConfig) Populate(logger *slog.Logger, vp *viper.Viper) {
c.AzureResourceGroup = vp.GetString(AzureResourceGroup)
c.AzureUsePrimaryAddress = vp.GetBool(AzureUsePrimaryAddress)
c.AzureUserAssignedIdentityID = vp.GetString(AzureUserAssignedIdentityID)
c.AzureReleaseExcessIPs = vp.GetBool(AzureReleaseExcessIPs)

// AlibabaCloud options

Expand Down
151 changes: 151 additions & 0 deletions pkg/azure/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,12 @@ func parseInterface(iface *armnetwork.Interface, subnets ipamTypes.SubnetMap, us
IP: *ip.Properties.PrivateIPAddress,
State: strings.ToLower(string(*ip.Properties.ProvisioningState)),
}
if ip.Name != nil {
addr.Name = *ip.Name
}
if ip.Properties.Primary != nil {
addr.Primary = *ip.Properties.Primary
}

if ip.Properties.Subnet != nil {
addr.Subnet = *ip.Properties.Subnet.ID
Expand Down Expand Up @@ -653,6 +659,151 @@ func (c *Client) AssignPrivateIpAddressesVM(ctx context.Context, subnetID, inter
return nil
}

// UnassignPrivateIpAddressesVM removes the given private IPs from an interface attached to a standalone instance
func (c *Client) UnassignPrivateIpAddressesVM(ctx context.Context, interfaceName string, addresses []string) error {
if len(addresses) == 0 {
return nil
}

c.limiter.Limit(ctx, interfacesGet)
sinceStart := spanstat.Start()

iface, err := c.interfaces.Get(ctx, c.resourceGroup, interfaceName, nil)

c.metricsAPI.ObserveAPICall(interfacesGet, deriveStatus(err), sinceStart.Seconds())
if err != nil {
return fmt.Errorf("failed to get standalone instance's interface %s: %w", interfaceName, err)
}

releaseSet := make(map[string]struct{}, len(addresses))
for _, ip := range addresses {
releaseSet[ip] = struct{}{}
}

kept := iface.Properties.IPConfigurations[:0]
for _, ipConfig := range iface.Properties.IPConfigurations {
if ipConfig.Properties != nil && ipConfig.Properties.Primary != nil && *ipConfig.Properties.Primary {
kept = append(kept, ipConfig)
continue
}
if ipConfig.Properties == nil || ipConfig.Properties.PrivateIPAddress == nil {
kept = append(kept, ipConfig)
continue
}
if _, drop := releaseSet[*ipConfig.Properties.PrivateIPAddress]; drop {
continue
}
kept = append(kept, ipConfig)
}
iface.Properties.IPConfigurations = kept

c.limiter.Limit(ctx, interfacesCreateOrUpdate)
sinceStart = spanstat.Start()

poller, err := c.interfaces.BeginCreateOrUpdate(ctx, c.resourceGroup, interfaceName, iface.Interface, nil)

defer func() {
c.metricsAPI.ObserveAPICall(interfacesCreateOrUpdate, deriveStatus(err), sinceStart.Seconds())
}()
if err != nil {
return fmt.Errorf("unable to update interface %s: %w", interfaceName, err)
}

if _, err := poller.PollUntilDone(ctx, nil); err != nil {
return fmt.Errorf("error while waiting for interface CreateOrUpdate to complete for %s: %w", interfaceName, err)
}

return nil
}

// UnassignPrivateIpAddressesVMSS removes the IPConfigurations with the given names from
// an interface attached to a VMSS instance. The caller is responsible for translating
// IPs to IPConfiguration names — typically using the cached AzureInterface populated by
// the InstancesManager — which avoids an additional Azure API call to fetch the NIC.
func (c *Client) UnassignPrivateIpAddressesVMSS(ctx context.Context, instanceID, vmssName, interfaceName string, ipConfigNames []string) error {
if len(ipConfigNames) == 0 {
return nil
}

dropNames := make(map[string]struct{}, len(ipConfigNames))
for _, name := range ipConfigNames {
dropNames[name] = struct{}{}
}

vmssGetOptions := &armcompute.VirtualMachineScaleSetVMsClientGetOptions{
Expand: to.Ptr(armcompute.InstanceViewTypesInstanceView),
}

c.limiter.Limit(ctx, virtualMachineScaleSetVMsGet)
sinceStart := spanstat.Start()

result, err := c.virtualMachineScaleSetVMs.Get(ctx, c.resourceGroup, vmssName, instanceID, vmssGetOptions)

c.metricsAPI.ObserveAPICall(virtualMachineScaleSetVMsGet, deriveStatus(err), sinceStart.Seconds())
if err != nil {
return fmt.Errorf("failed to get VM %s from VMSS %s: %w", instanceID, vmssName, err)
}

var netIfConfig *armcompute.VirtualMachineScaleSetNetworkConfiguration
if result.Properties.NetworkProfileConfiguration != nil {
for _, networkInterfaceConfiguration := range result.Properties.NetworkProfileConfiguration.NetworkInterfaceConfigurations {
if networkInterfaceConfiguration.Name != nil && *networkInterfaceConfiguration.Name == interfaceName {
netIfConfig = networkInterfaceConfiguration
break
}
}
}

if netIfConfig == nil {
return fmt.Errorf("interface %s does not exist in VM %s", interfaceName, instanceID)
}

kept := netIfConfig.Properties.IPConfigurations[:0]
for _, ipConfig := range netIfConfig.Properties.IPConfigurations {
if ipConfig.Properties != nil && ipConfig.Properties.Primary != nil && *ipConfig.Properties.Primary {
kept = append(kept, ipConfig)
continue
}
if ipConfig.Name == nil {
kept = append(kept, ipConfig)
continue
}
if _, drop := dropNames[*ipConfig.Name]; drop {
continue
}
kept = append(kept, ipConfig)
}
netIfConfig.Properties.IPConfigurations = kept

// Unset imageReference, because if this contains a reference to an image from the
// Azure Compute Gallery, including this reference in an update to the VMSS instance
// will cause a permissions error, because the reference includes an Azure-managed
// subscription ID.
// Removing the image reference indicates to the API that we don't want to change it.
// See https://github.com/Azure/AKS/issues/1819.
if result.Properties.StorageProfile != nil {
result.Properties.StorageProfile.ImageReference = nil
}

c.limiter.Limit(ctx, virtualMachineScaleSetVMsUpdate)
sinceStart = spanstat.Start()

poller, err := c.virtualMachineScaleSetVMs.BeginUpdate(ctx, c.resourceGroup, vmssName, instanceID, result.VirtualMachineScaleSetVM, nil)

defer func() {
c.metricsAPI.ObserveAPICall(virtualMachineScaleSetVMsUpdate, deriveStatus(err), sinceStart.Seconds())
}()
if err != nil {
return fmt.Errorf("unable to update virtualMachineScaleSetVMs: %w", err)
}

if _, err := poller.PollUntilDone(ctx, nil); err != nil {
return fmt.Errorf("error while waiting for virtualMachineScaleSetVMs Update to complete: %w", err)
}

return nil
}

// AssignPublicIPAddressesVMSS assigns a public IP to a VMSS instance.
// The public IP is allocated from a Public IP Prefix matching publicIpTags
func (c *Client) AssignPublicIPAddressesVMSS(ctx context.Context, instanceID, vmssName string, publicIpTags ipamTypes.Tags) (string, error) {
Expand Down
69 changes: 69 additions & 0 deletions pkg/azure/api/mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
GetInstances
GetVpcsAndSubnets
AssignPrivateIpAddressesVMSS
UnassignPrivateIpAddressesVMSS
MaxOperation
)

Expand Down Expand Up @@ -234,6 +235,7 @@ func (a *API) AssignPrivateIpAddressesVMSS(ctx context.Context, vmName, vmssName
IP: ip.String(),
Subnet: subnetID,
State: types.StateSucceeded,
Name: "Cilium-mock-" + ip.String(),
})
}

Expand All @@ -253,6 +255,73 @@ func (a *API) AssignPrivateIpAddressesVMSS(ctx context.Context, vmName, vmssName
return nil
}

func (a *API) UnassignPrivateIpAddressesVM(ctx context.Context, interfaceName string, addresses []string) error {
return nil
}

func (a *API) UnassignPrivateIpAddressesVMSS(ctx context.Context, vmName, vmssName, interfaceName string, ipConfigNames []string) error {
a.rateLimit()
a.delaySim.Delay(UnassignPrivateIpAddressesVMSS)

a.mutex.Lock()
defer a.mutex.Unlock()

if err, ok := a.errors[UnassignPrivateIpAddressesVMSS]; ok {
return err
}

if len(ipConfigNames) == 0 {
return nil
}

dropNames := make(map[string]struct{}, len(ipConfigNames))
for _, name := range ipConfigNames {
dropNames[name] = struct{}{}
}

foundInterface := false
instances := a.instances.DeepCopy()
err := instances.ForeachInterface("", func(id, _ string, iface ipamTypes.InterfaceRevision) error {
intf, ok := iface.Resource.(*types.AzureInterface)
if !ok {
return fmt.Errorf("invalid interface object")
}

if intf.Name != interfaceName || intf.GetVMID() != vmName {
return nil
}

kept := intf.Addresses[:0]
for _, addr := range intf.Addresses {
if _, drop := dropNames[addr.Name]; drop {
if s, ok := a.subnets[addr.Subnet]; ok {
_, ipNet, err := net.ParseCIDR(addr.IP + "/32")
if err == nil {
s.allocator.Release(ipNet.IP)
}
}
continue
}
kept = append(kept, addr)
}
intf.Addresses = kept

foundInterface = true
return nil
})
if err != nil {
return err
}

a.updateInstancesLocked(instances)

if !foundInterface {
return fmt.Errorf("interface %s not found", interfaceName)
}

return nil
}

func (a *API) AssignPublicIPAddressesVMSS(ctx context.Context, instanceID, vmssName string, publicIpTags ipamTypes.Tags) (string, error) {
a.rateLimit()
return "mock-public-ip-prefix-id", nil
Expand Down
2 changes: 2 additions & 0 deletions pkg/azure/ipam/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type AzureAPI interface {
GetVpcsAndSubnets(ctx context.Context) (ipamTypes.VirtualNetworkMap, ipamTypes.SubnetMap, error)
AssignPrivateIpAddressesVM(ctx context.Context, subnetID, interfaceName string, addresses int) error
AssignPrivateIpAddressesVMSS(ctx context.Context, instanceID, vmssName, subnetID, interfaceName string, addresses int) error
UnassignPrivateIpAddressesVM(ctx context.Context, interfaceName string, addresses []string) error
UnassignPrivateIpAddressesVMSS(ctx context.Context, instanceID, vmssName, interfaceName string, ipConfigNames []string) error
AssignPublicIPAddressesVM(ctx context.Context, instanceID string, publicIpTags ipamTypes.Tags) (string, error)
AssignPublicIPAddressesVMSS(ctx context.Context, instanceID, vmssName string, publicIpTags ipamTypes.Tags) (string, error)
}
Expand Down
Loading
Loading