pkg/provider/branch/trunk/trunk.go (549 lines of code) (raw):
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
package trunk
import (
"encoding/json"
"fmt"
"slices"
"strconv"
"strings"
"sync"
"time"
"github.com/aws/amazon-vpc-resource-controller-k8s/pkg/aws/ec2"
"github.com/aws/amazon-vpc-resource-controller-k8s/pkg/aws/ec2/api"
ec2Errors "github.com/aws/amazon-vpc-resource-controller-k8s/pkg/aws/errors"
"github.com/aws/amazon-vpc-resource-controller-k8s/pkg/aws/vpc"
"github.com/aws/amazon-vpc-resource-controller-k8s/pkg/config"
"github.com/aws/amazon-vpc-resource-controller-k8s/pkg/provider/branch/cooldown"
"github.com/samber/lo"
"github.com/aws/aws-sdk-go-v2/aws"
awsEc2 "github.com/aws/aws-sdk-go-v2/service/ec2"
ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/go-logr/logr"
"github.com/prometheus/client_golang/prometheus"
v1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/metrics"
)
const (
// MaxAllocatableVlanIds is the maximum number of Vlan Ids that can be allocated per trunk.
MaxAllocatableVlanIds = 121
// MaxDeleteRetries is the maximum number of times the ENI will be retried before being removed from the delete queue
MaxDeleteRetries = 3
)
var (
InterfaceTypeTrunk = "trunk"
TrunkEniDescription = "trunk-eni"
BranchEniDescription = "branch-eni"
)
var ErrCurrentlyAtMaxCapacity = fmt.Errorf("cannot create more branches at this point as used branches plus the " +
"delete queue is at max capacity")
var (
trunkENIOperationsErrCount = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "trunk_eni_operations_err_count",
Help: "The number of errors encountered for operations on Trunk ENI",
},
[]string{"operation"},
)
unreconciledTrunkENICount = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "unreconciled_trunk_network_interfaces",
Help: "The number of unreconciled trunk network interfaces",
},
[]string{"attribute"},
)
branchENIOperationsSuccessCount = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "branch_eni_opeartions_success_count",
Help: "The number of branch ENI succeeded operations",
},
[]string{"operation"},
)
branchENIOperationsFailureCount = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "branch_eni_opeartions_failure_count",
Help: "The number of branch ENI failed operations",
},
[]string{"operation"},
)
prometheusRegistered = false
)
type TrunkENI interface {
// InitTrunk initializes trunk interface
InitTrunk(instance ec2.EC2Instance, pods []v1.Pod) error
// CreateAndAssociateBranchENIs creates and associate branch interface/s to trunk interface
CreateAndAssociateBranchENIs(pod *v1.Pod, securityGroups []string, eniCount int) ([]*ENIDetails, error)
// PushBranchENIsToCoolDownQueue pushes the branch interface belonging to the pod to the cool down queue
PushBranchENIsToCoolDownQueue(UID string)
// DeleteCooledDownENIs deletes the interfaces that have been sitting in the queue for cool down period
DeleteCooledDownENIs()
// Reconcile compares the cache state with the list of pods to identify events that were missed and clean up the dangling interfaces
Reconcile(pods []v1.Pod) bool
// PushENIsToFrontOfDeleteQueue pushes the eni network interfaces to the front of the delete queue
PushENIsToFrontOfDeleteQueue(*v1.Pod, []*ENIDetails)
// Introspect returns the state of the Trunk ENI
Introspect() IntrospectResponse
}
// trunkENI is the first trunk network interface of an instance
type trunkENI struct {
// Log is the logger with the instance details
log logr.Logger
// lock is used to perform concurrent operation on the shared variables like the list of used vlan ids
lock sync.RWMutex
// ec2ApiHelper is the wrapper interface that provides EC2 API helper functions
ec2ApiHelper api.EC2APIHelper
// trunkENIId is the interface id of the trunk network interface
trunkENIId string
// instance is the pointer to the instance details
instance ec2.EC2Instance
// usedVlanIds is the list of boolean value representing the used vlan ids
usedVlanIds []bool
// branchENIs is the list of BranchENIs associated with the trunk
uidToBranchENIMap map[string][]*ENIDetails
// deleteQueue is the queue of ENIs that are being cooled down before being deleted
deleteQueue []*ENIDetails
// nodeName tag is the tag added to trunk and branch ENIs created on the node
nodeIDTag []ec2types.Tag
}
// PodENI is a json convertible structure that stores the Branch ENI details that can be
// used by the CNI plugin or the component consuming the resource
type ENIDetails struct {
// BranchENId is the network interface id of the branch interface
ID string `json:"eniId"`
// MacAdd is the MAC address of the network interface
MACAdd string `json:"ifAddress"`
// IPv4 and/or IPv6 address assigned to the branch Network interface
IPV4Addr string `json:"privateIp"`
IPV6Addr string `json:"ipv6Addr"`
// VlanId is the VlanId of the branch network interface
VlanID int `json:"vlanId"`
// SubnetCIDR is the CIDR block of the subnet
SubnetCIDR string `json:"subnetCidr"`
SubnetV6CIDR string `json:"subnetV6Cidr"`
// deletionTimeStamp is the time when the pod was marked deleted.
deletionTimeStamp time.Time
// deleteRetryCount is the
deleteRetryCount int
// ID of association between branch and trunk ENI
AssociationID string `json:"associationID"`
}
type IntrospectResponse struct {
TrunkENIID string
InstanceID string
PodToBranchENI map[string][]ENIDetails
DeleteQueue []ENIDetails
}
type IntrospectSummaryResponse struct {
TrunkENIID string
InstanceID string
BranchENICount int
DeleteQueueLen int
}
// NewTrunkENI returns a new Trunk ENI interface.
func NewTrunkENI(logger logr.Logger, instance ec2.EC2Instance, helper api.EC2APIHelper) TrunkENI {
availVlans := make([]bool, MaxAllocatableVlanIds)
// VlanID 0 cannot be assigned.
availVlans[0] = true
return &trunkENI{
log: logger,
usedVlanIds: availVlans,
ec2ApiHelper: helper,
instance: instance,
uidToBranchENIMap: make(map[string][]*ENIDetails),
nodeIDTag: []ec2types.Tag{
{
Key: aws.String(config.NetworkInterfaceNodeIDKey),
Value: aws.String(instance.InstanceID()),
},
},
}
}
func PrometheusRegister() {
if !prometheusRegistered {
metrics.Registry.MustRegister(trunkENIOperationsErrCount)
metrics.Registry.MustRegister(unreconciledTrunkENICount)
metrics.Registry.MustRegister(branchENIOperationsSuccessCount)
metrics.Registry.MustRegister(branchENIOperationsFailureCount)
prometheusRegistered = true
}
}
// InitTrunk initializes the trunk network interface and all it's associated branch network interfaces by making calls
// to EC2 API
func (t *trunkENI) InitTrunk(instance ec2.EC2Instance, podList []v1.Pod) error {
instanceID := t.instance.InstanceID()
log := t.log.WithValues("request", "initialize", "instance ID", instanceID)
nwInterfaces, err := t.ec2ApiHelper.GetInstanceNetworkInterface(&instanceID)
if err != nil {
trunkENIOperationsErrCount.WithLabelValues("describe_instance_nw_interface").Inc()
return err
}
var trunk ec2types.InstanceNetworkInterface
// Get trunk network interface
for _, nwInterface := range nwInterfaces {
// It's possible to get an empty network interface response if the instance is being deleted.
if nwInterface.InterfaceType == nil {
return fmt.Errorf("received an empty network interface response "+
"from EC2 %+v", nwInterface)
}
if *nwInterface.InterfaceType == "trunk" {
// Check that the trunkENI is in attached state before adding to cache
if err = t.ec2ApiHelper.WaitForNetworkInterfaceStatusChange(nwInterface.NetworkInterfaceId, string(ec2types.AttachmentStatusAttached)); err == nil {
t.trunkENIId = *nwInterface.NetworkInterfaceId
} else {
return fmt.Errorf("failed to verify network interface status attached for %v", *nwInterface.NetworkInterfaceId)
}
trunk = nwInterface
}
}
// Trunk interface doesn't exists, try to create a new trunk interface
if t.trunkENIId == "" {
freeIndex, err := instance.GetHighestUnusedDeviceIndex()
if err != nil {
trunkENIOperationsErrCount.WithLabelValues("find_free_index").Inc()
log.Error(err, "failed to find free device index")
return err
}
trunk, err := t.ec2ApiHelper.CreateAndAttachNetworkInterface(&instanceID, aws.String(t.instance.SubnetID()),
t.instance.CurrentInstanceSecurityGroups(), t.nodeIDTag, &freeIndex, &TrunkEniDescription, &InterfaceTypeTrunk, nil)
if err != nil {
trunkENIOperationsErrCount.WithLabelValues("create_trunk_eni").Inc()
return err
}
t.trunkENIId = *trunk.NetworkInterfaceId
log.Info("created a new trunk interface", "trunk id", t.trunkENIId)
return nil
}
// the node already have trunk, let's check if its SGs and Subnets match with expected
expectedSubnetID, expectedSecurityGroups := t.instance.GetCustomNetworkingSpec()
if len(expectedSecurityGroups) > 0 || expectedSubnetID != "" {
slices.Sort(expectedSecurityGroups)
trunkSGs := lo.Map(trunk.Groups, func(g ec2types.GroupIdentifier, _ int) string {
return lo.FromPtr(g.GroupId)
})
slices.Sort(trunkSGs)
mismatchedSubnets := expectedSubnetID != lo.FromPtr(trunk.SubnetId)
mismatchedSGs := !slices.Equal(expectedSecurityGroups, trunkSGs)
extraSGsInTrunk, missingSGsInTrunk := lo.Difference(trunkSGs, expectedSecurityGroups)
t.log.Info("Observed trunk ENI config",
"instanceID", t.instance.InstanceID(),
"trunkENIID", lo.FromPtr(trunk.NetworkInterfaceId),
"configuredTrunkSGs", trunkSGs,
"configuredTrunkSubnet", lo.FromPtr(trunk.SubnetId),
"desiredTrunkSGs", expectedSecurityGroups,
"desiredTrunkSubnet", expectedSubnetID,
"mismatchedSGs", mismatchedSGs,
"mismatchedSubnets", mismatchedSubnets,
"missingSGs", missingSGsInTrunk,
"extraSGs", extraSGsInTrunk,
)
if mismatchedSGs {
unreconciledTrunkENICount.WithLabelValues("security_groups").Inc()
}
if mismatchedSubnets {
unreconciledTrunkENICount.WithLabelValues("subnet").Inc()
}
}
// Get the list of branch ENIs
branchInterfaces, err := t.ec2ApiHelper.GetBranchNetworkInterface(&t.trunkENIId, aws.String(t.instance.SubnetID()))
if err != nil {
return err
}
// Convert the list of interfaces to a set
associatedBranchInterfaces := make(map[string]*ec2types.NetworkInterface)
for _, branchInterface := range branchInterfaces {
associatedBranchInterfaces[*branchInterface.NetworkInterfaceId] = branchInterface
}
// From the list of pods on the given node, and the branch ENIs from EC2 API call rebuild the internal cache
for _, pod := range podList {
pod := pod // Fix gosec G601, so we can use &node
eniListFromPod := t.getBranchInterfacesUsedByPod(&pod)
if len(eniListFromPod) == 0 {
continue
}
var branchENIs []*ENIDetails
for _, eni := range eniListFromPod {
_, isPresent := associatedBranchInterfaces[eni.ID]
if !isPresent {
t.log.Error(fmt.Errorf("eni allocated to pod not found in ec2"), "eni not found", "eni", eni)
trunkENIOperationsErrCount.WithLabelValues("get_branch_eni_from_ec2").Inc()
continue
}
// Mark the Vlan ID from the pod's annotation
t.markVlanAssigned(eni.VlanID)
branchENIs = append(branchENIs, eni)
delete(associatedBranchInterfaces, eni.ID)
}
t.uidToBranchENIMap[string(pod.UID)] = branchENIs
}
// Delete the branch ENI that don't belong to any pod.
for _, branchInterface := range associatedBranchInterfaces {
t.log.Info("pushing eni to delete queue as no pod owns it", "eni",
*branchInterface.NetworkInterfaceId)
vlanId, err := t.getVlanIdFromTag(branchInterface.TagSet)
if err != nil {
trunkENIOperationsErrCount.WithLabelValues("get_vlan_from_tag").Inc()
log.Error(err, "failed to find vlan id", "interface", *branchInterface.NetworkInterfaceId)
continue
}
// Even thought the ENI is going to be deleted still mark Vlan ID assigned as ENI will sit in cool down queue for a while
t.markVlanAssigned(vlanId)
t.pushENIToDeleteQueue(&ENIDetails{
ID: *branchInterface.NetworkInterfaceId,
VlanID: vlanId,
deletionTimeStamp: time.Now(),
})
}
log.V(1).Info("successfully initialized trunk with all associated branch interfaces",
"trunk", t.trunkENIId, "branch interfaces", t.uidToBranchENIMap)
return nil
}
// Reconcile reconciles the state from the API Server to the internal cache of EC2 Branch Interfaces, if the controller
// missed some delete events the reconcile method will perform cleanup for the dangling interfaces
func (t *trunkENI) Reconcile(pods []v1.Pod) bool {
// Perform under lock to block new pods being added/removed concurrently
t.lock.Lock()
defer t.lock.Unlock()
currentPodSet := make(map[string]struct{})
var isPresent struct{}
for _, pod := range pods {
currentPodSet[string(pod.UID)] = isPresent
}
leakedENIs := 0
for uid, branchENIs := range t.uidToBranchENIMap {
_, exists := currentPodSet[uid]
if !exists {
leakedENIs += 1
branchENIOperationsSuccessCount.WithLabelValues("leaked_branch_enis").Inc()
for _, eni := range branchENIs {
// Pod could have been deleted recently, set the timestamp to current time as controller is not aware of the actual time.
eni.deletionTimeStamp = time.Now()
t.deleteQueue = append(t.deleteQueue, eni)
}
delete(t.uidToBranchENIMap, uid)
t.log.Info("leaked eni pushed to delete queue, deleted non-existing pod", "pod uid", uid, "eni", branchENIs)
}
}
return leakedENIs > 0
}
// CreateAndAssociateBranchToTrunk creates a new branch network interface and associates the branch to the trunk
// network interface. It returns a Json convertible structure which has all the required details of the branch ENI
func (t *trunkENI) CreateAndAssociateBranchENIs(pod *v1.Pod, securityGroups []string, eniCount int) ([]*ENIDetails, error) {
log := t.log.WithValues("request", "create", "pod namespace", pod.Namespace, "pod name", pod.Name)
branchENI, isPresent := t.getBranchFromCache(string(pod.UID))
if isPresent {
// Possible when older pod with same namespace and name is still being deleted
return nil, fmt.Errorf("cannot create new eni entry already exist, older entry : %v", branchENI)
}
if !t.canCreateMore() {
return nil, ErrCurrentlyAtMaxCapacity
}
// If the security group is empty use the instance security group
if securityGroups == nil || len(securityGroups) == 0 {
securityGroups = t.instance.CurrentInstanceSecurityGroups()
}
var newENIs []*ENIDetails
var err error
var nwInterface *ec2types.NetworkInterface
var vlanID int
for i := 0; i < eniCount; i++ {
// Assign VLAN
vlanID, err = t.assignVlanId()
if err != nil {
err = fmt.Errorf("assigning vlad id, %w", err)
trunkENIOperationsErrCount.WithLabelValues("assign_vlan_id").Inc()
break
}
// Vlan ID tag workaround, as describe trunk association is not supported with assumed role
tags := []ec2types.Tag{
{
Key: aws.String(config.VLandIDTag),
Value: aws.String(strconv.Itoa(vlanID)),
},
{
Key: aws.String(config.TrunkENIIDTag),
Value: &t.trunkENIId,
},
}
// append the nodeName tag to add to branch ENIs
tags = append(tags, t.nodeIDTag...)
// Create Branch ENI
nwInterface, err = t.ec2ApiHelper.CreateNetworkInterface(&BranchEniDescription,
aws.String(t.instance.SubnetID()), securityGroups, tags, nil, nil)
if err != nil {
err = fmt.Errorf("creating network interface, %w", err)
t.freeVlanId(vlanID)
branchENIOperationsFailureCount.WithLabelValues("creating_branch_eni_failed").Inc()
break
} else {
branchENIOperationsSuccessCount.WithLabelValues("created_branch_eni_succeeded").Inc()
}
// Branch ENI can have an IPv4 address, IPv6 address, or both
var v4Addr, v6Addr string
if nwInterface.PrivateIpAddress != nil {
v4Addr = *nwInterface.PrivateIpAddress
}
if nwInterface.Ipv6Address != nil {
v6Addr = *nwInterface.Ipv6Address
}
newENI := &ENIDetails{
ID: *nwInterface.NetworkInterfaceId, MACAdd: *nwInterface.MacAddress,
IPV4Addr: v4Addr, IPV6Addr: v6Addr, SubnetCIDR: t.instance.SubnetCidrBlock(),
SubnetV6CIDR: t.instance.SubnetV6CidrBlock(), VlanID: vlanID,
}
newENIs = append(newENIs, newENI)
// Associate Branch to trunk
var associationOutput *awsEc2.AssociateTrunkInterfaceOutput
associationOutput, err = t.ec2ApiHelper.AssociateBranchToTrunk(&t.trunkENIId, nwInterface.NetworkInterfaceId, vlanID)
if err != nil {
err = fmt.Errorf("associating branch to trunk, %w", err)
trunkENIOperationsErrCount.WithLabelValues("associate_branch").Inc()
break
}
newENI.AssociationID = *associationOutput.InterfaceAssociation.AssociationId
}
if err != nil {
log.Error(err, "failed to create ENI, moving the ENI to delete list")
// Moving to delete list, because it has all the retrying logic in case of failure
t.PushENIsToFrontOfDeleteQueue(nil, newENIs)
return nil, err
}
t.addBranchToCache(string(pod.UID), newENIs)
log.Info("successfully created branch interfaces", "interfaces", newENIs,
"security group used", securityGroups)
return newENIs, nil
}
// DeleteBranchNetworkInterface deletes the branch network interface and returns an error in case of failure to delete
func (t *trunkENI) PushBranchENIsToCoolDownQueue(UID string) {
// Lock is required as Reconciler is also performing operation concurrently
t.lock.Lock()
defer t.lock.Unlock()
branchENIs, isPresent := t.uidToBranchENIMap[UID]
if !isPresent {
t.log.Info("couldn't find Branch ENI in cache, it could have been released if pod"+
"succeeded/failed before being deleted", "UID", UID)
trunkENIOperationsErrCount.WithLabelValues("get_branch_from_cache").Inc()
return
}
for _, eni := range branchENIs {
eni.deletionTimeStamp = time.Now()
t.deleteQueue = append(t.deleteQueue, eni)
}
delete(t.uidToBranchENIMap, UID)
t.log.Info("moved branch network interfaces to delete queue", "Interfaces",
branchENIs, "UID", UID)
}
func (t *trunkENI) DeleteCooledDownENIs() {
for eni, hasENI := t.popENIFromDeleteQueue(); hasENI; eni, hasENI = t.popENIFromDeleteQueue() {
if eni.deletionTimeStamp.IsZero() ||
time.Now().After(eni.deletionTimeStamp.Add(cooldown.GetCoolDown().GetCoolDownPeriod())) {
err := t.deleteENI(eni)
if err != nil {
eni.deleteRetryCount++
if eni.deleteRetryCount >= MaxDeleteRetries {
t.log.Error(err, "forgetting eni as max retries exceeded", "eni", eni)
// TODO: free vlan id?
continue
}
t.log.Error(err, "failed to delete eni, will retry", "eni", eni)
t.PushENIsToFrontOfDeleteQueue(nil, []*ENIDetails{eni})
continue
}
t.log.V(1).Info("deleted eni successfully", "eni", eni, "deletion time", time.Now(),
"pushed to queue time", eni.deletionTimeStamp)
} else {
// Since the current item is not cooled down so the items added after it would not be cooled down either
t.PushENIsToFrontOfDeleteQueue(nil, []*ENIDetails{eni})
return
}
}
}
// deleteENIs deletes the provided ENIs and frees up the Vlan assigned to then
func (t *trunkENI) deleteENI(eniDetail *ENIDetails) (err error) {
// Disassociate branch ENI from trunk if association ID exists and delete branch network interface
if eniDetail.AssociationID != "" {
err = t.ec2ApiHelper.DisassociateTrunkInterface(&eniDetail.AssociationID)
if err != nil {
trunkENIOperationsErrCount.WithLabelValues("disassociate_trunk_error").Inc()
if !strings.Contains(err.Error(), ec2Errors.NotFoundAssociationID) {
t.log.Error(err, "failed to disassociate branch ENI from trunk, will try to delete the branch ENI")
// Not returning error here, fallback to force branch ENI deletion
} else {
t.log.Info("AssociationID not found when disassociating branch from trunk ENI, it is already disassociated so delete the branch ENI")
}
}
}
err = t.ec2ApiHelper.DeleteNetworkInterface(&eniDetail.ID)
if err != nil {
branchENIOperationsFailureCount.WithLabelValues("delete_branch_error").Inc()
if !strings.Contains(err.Error(), ec2Errors.NotFoundInterfaceID) {
t.log.Error(err, "calling EC2 delete API to delete the branch ENI failed", "BranchENI", eniDetail)
return err
} else {
t.log.Info("The branch ENI was not found by EC2. Will not call EC2 for deletion again", "BranchENI", eniDetail, "Error", err.Error())
}
}
branchENIOperationsSuccessCount.WithLabelValues("deleted_branch_succesfully").Inc()
t.log.Info("deleted eni", "eni details", eniDetail)
// Free vlan id used by the branch ENI
if eniDetail.VlanID != 0 {
t.freeVlanId(eniDetail.VlanID)
}
return nil
}
func (t *trunkENI) getBranchInterfaceMap(eniList []*ENIDetails) map[string]*ENIDetails {
eniMap := make(map[string]*ENIDetails)
for _, eni := range eniList {
eniMap[eni.ID] = eni
}
return eniMap
}
func (t *trunkENI) getBranchInterfacesUsedByPod(pod *v1.Pod) (eniDetails []*ENIDetails) {
branchAnnotation, isPresent := pod.Annotations[config.ResourceNamePodENI]
if !isPresent {
return
}
if err := json.Unmarshal([]byte(branchAnnotation), &eniDetails); err != nil {
t.log.Error(err, "failed to unmarshal resource annotation", "annotation", branchAnnotation)
}
return
}
// pushENIToDeleteQueue pushes an ENI to a delete queue
func (t *trunkENI) pushENIToDeleteQueue(eni *ENIDetails) {
t.lock.Lock()
defer t.lock.Unlock()
t.deleteQueue = append(t.deleteQueue, eni)
}
// pushENIsToFrontOfDeleteQueue pushes the ENI list to the front of the delete queue
func (t *trunkENI) PushENIsToFrontOfDeleteQueue(pod *v1.Pod, eniList []*ENIDetails) {
t.lock.Lock()
defer t.lock.Unlock()
if pod != nil {
t.log.Info("pushing ENIs to delete queue and removing pod from cache",
"uid", pod.UID, "ENIs", eniList)
delete(t.uidToBranchENIMap, string(pod.UID))
} else {
t.log.Info("pushing ENIs to delete queue", "ENIs", eniList)
}
t.deleteQueue = append(eniList, t.deleteQueue...)
}
// popENIFromDeleteQueue pops an ENI from delete queue, if the queue is empty then the false is returned
func (t *trunkENI) popENIFromDeleteQueue() (eni *ENIDetails, hasENI bool) {
t.lock.Lock()
defer t.lock.Unlock()
if len(t.deleteQueue) > 0 {
eni = t.deleteQueue[0]
hasENI = true
t.deleteQueue = t.deleteQueue[1:]
}
return eni, hasENI
}
// addBranchToCache adds the given branch to the cache if not already present
func (t *trunkENI) addBranchToCache(UID string, branchENIs []*ENIDetails) {
t.lock.Lock()
defer t.lock.Unlock()
if _, ok := t.uidToBranchENIMap[UID]; ok {
t.log.Info("branch eni already exist not adding again", "request", branchENIs)
return
}
t.uidToBranchENIMap[UID] = branchENIs
}
// getBranchFromCache returns the branch from the cache
func (t *trunkENI) getBranchFromCache(UID string) (branchENIs []*ENIDetails, isPresent bool) {
t.lock.RLock()
defer t.lock.RUnlock()
branchENIs, isPresent = t.uidToBranchENIMap[UID]
return
}
// assignVlanId assigns a free vlan id from the list of available vlan ids. In the future this can be changed to LL
func (t *trunkENI) assignVlanId() (int, error) {
t.lock.Lock()
defer t.lock.Unlock()
for index, used := range t.usedVlanIds {
if !used {
t.usedVlanIds[index] = true
return index, nil
}
}
return 0, fmt.Errorf("failed to find free vlan id in the available %d ids", len(t.usedVlanIds))
}
// markVlanAssigned marks a vlan Id as assigned if not used
func (t *trunkENI) markVlanAssigned(vlanId int) {
t.lock.Lock()
defer t.lock.Unlock()
t.usedVlanIds[vlanId] = true
}
// freeVlanId frees a vlan ID currently used by a network interface
func (t *trunkENI) freeVlanId(vlanId int) {
t.lock.Lock()
defer t.lock.Unlock()
isUsed := t.usedVlanIds[vlanId]
if !isUsed {
trunkENIOperationsErrCount.WithLabelValues("free_unused_vlan_id").Inc()
t.log.Error(fmt.Errorf("failed to free a unused vlan id"), "", "vlan id", vlanId)
return
}
t.usedVlanIds[vlanId] = false
}
func (t *trunkENI) getVlanIdFromTag(tags []ec2types.Tag) (int, error) {
for _, tag := range tags {
if *tag.Key == config.VLandIDTag {
return strconv.Atoi(*tag.Value)
}
}
return 0, fmt.Errorf("failed to find vlan tag from the list of tags")
}
func (t *trunkENI) canCreateMore() bool {
t.lock.RLock()
defer t.lock.RUnlock()
var usedBranches int
for _, branches := range t.uidToBranchENIMap {
usedBranches += len(branches)
}
if usedBranches+len(t.deleteQueue) < vpc.Limits[t.instance.Type()].BranchInterface {
return true
}
return false
}
func (t *trunkENI) Introspect() IntrospectResponse {
t.lock.RLock()
defer t.lock.RUnlock()
response := IntrospectResponse{
TrunkENIID: t.trunkENIId,
InstanceID: t.instance.InstanceID(),
PodToBranchENI: make(map[string][]ENIDetails),
}
for uid, allENI := range t.uidToBranchENIMap {
var eniDetails []ENIDetails
for _, eni := range allENI {
eniDetails = append(eniDetails, *eni)
}
response.PodToBranchENI[uid] = eniDetails
}
for _, eni := range t.deleteQueue {
response.DeleteQueue = append(response.DeleteQueue, *eni)
}
return response
}