github.com/cilium/cilium@v1.16.2/pkg/azure/api/mock/mock.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package mock
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"net"
    10  	"time"
    11  
    12  	"golang.org/x/time/rate"
    13  
    14  	"github.com/cilium/cilium/pkg/api/helpers"
    15  	"github.com/cilium/cilium/pkg/azure/types"
    16  	"github.com/cilium/cilium/pkg/ipam/service/ipallocator"
    17  	ipamTypes "github.com/cilium/cilium/pkg/ipam/types"
    18  	"github.com/cilium/cilium/pkg/lock"
    19  )
    20  
    21  // Operation is an Azure API operation that this mock API supports
    22  type Operation int
    23  
    24  const (
    25  	AllOperations Operation = iota
    26  	GetInstances
    27  	GetVpcsAndSubnets
    28  	AssignPrivateIpAddressesVMSS
    29  	MaxOperation
    30  )
    31  
    32  type subnet struct {
    33  	subnet    *ipamTypes.Subnet
    34  	allocator *ipallocator.Range
    35  }
    36  
    37  type API struct {
    38  	mutex     lock.RWMutex
    39  	subnets   map[string]*subnet
    40  	instances *ipamTypes.InstanceMap
    41  	vnets     map[string]*ipamTypes.VirtualNetwork
    42  	errors    map[Operation]error
    43  	delaySim  *helpers.DelaySimulator
    44  	limiter   *rate.Limiter
    45  }
    46  
    47  func NewAPI(subnets []*ipamTypes.Subnet, vnets []*ipamTypes.VirtualNetwork) *API {
    48  	api := &API{
    49  		instances: ipamTypes.NewInstanceMap(),
    50  		subnets:   map[string]*subnet{},
    51  		vnets:     map[string]*ipamTypes.VirtualNetwork{},
    52  		errors:    map[Operation]error{},
    53  		delaySim:  helpers.NewDelaySimulator(),
    54  	}
    55  
    56  	api.UpdateSubnets(subnets)
    57  
    58  	for _, v := range vnets {
    59  		api.vnets[v.ID] = v
    60  	}
    61  
    62  	return api
    63  }
    64  
    65  func (a *API) UpdateSubnets(subnets []*ipamTypes.Subnet) {
    66  	a.mutex.Lock()
    67  	a.subnets = map[string]*subnet{}
    68  	for _, s := range subnets {
    69  		_, cidr, _ := net.ParseCIDR(s.CIDR.String())
    70  
    71  		a.subnets[s.ID] = &subnet{
    72  			subnet:    s.DeepCopy(),
    73  			allocator: ipallocator.NewCIDRRange(cidr),
    74  		}
    75  	}
    76  	a.mutex.Unlock()
    77  }
    78  
    79  func (a *API) UpdateInstances(instances *ipamTypes.InstanceMap) {
    80  	a.mutex.Lock()
    81  	a.updateInstancesLocked(instances)
    82  	a.mutex.Unlock()
    83  }
    84  
    85  func (a *API) updateInstancesLocked(instances *ipamTypes.InstanceMap) {
    86  	a.instances = instances.DeepCopy()
    87  }
    88  
    89  // SetMockError modifies the mock API to return an error for a particular
    90  // operation
    91  func (a *API) SetMockError(op Operation, err error) {
    92  	a.mutex.Lock()
    93  	a.errors[op] = err
    94  	a.mutex.Unlock()
    95  }
    96  
    97  // SetDelay specifies the delay which should be simulated for an individual
    98  // Azure API operation
    99  func (a *API) SetDelay(op Operation, delay time.Duration) {
   100  	if op == AllOperations {
   101  		for op := AllOperations + 1; op < MaxOperation; op++ {
   102  			a.delaySim.SetDelay(op, delay)
   103  		}
   104  	} else {
   105  		a.delaySim.SetDelay(op, delay)
   106  	}
   107  }
   108  
   109  // SetLimiter adds a rate limiter to all simulated API calls
   110  func (a *API) SetLimiter(limit float64, burst int) {
   111  	a.limiter = rate.NewLimiter(rate.Limit(limit), burst)
   112  }
   113  
   114  func (a *API) rateLimit() {
   115  	a.mutex.RLock()
   116  	if a.limiter == nil {
   117  		a.mutex.RUnlock()
   118  		return
   119  	}
   120  
   121  	r := a.limiter.Reserve()
   122  	a.mutex.RUnlock()
   123  	if delay := r.Delay(); delay != time.Duration(0) && delay != rate.InfDuration {
   124  		time.Sleep(delay)
   125  	}
   126  }
   127  
   128  func (a *API) GetInstances(ctx context.Context, subnets ipamTypes.SubnetMap) (*ipamTypes.InstanceMap, error) {
   129  	a.rateLimit()
   130  	a.delaySim.Delay(GetInstances)
   131  
   132  	a.mutex.RLock()
   133  	defer a.mutex.RUnlock()
   134  
   135  	if err, ok := a.errors[GetInstances]; ok {
   136  		return nil, err
   137  	}
   138  
   139  	return a.instances.DeepCopy(), nil
   140  }
   141  
   142  func (a *API) GetVpcsAndSubnets(ctx context.Context) (ipamTypes.VirtualNetworkMap, ipamTypes.SubnetMap, error) {
   143  	a.rateLimit()
   144  	a.delaySim.Delay(GetVpcsAndSubnets)
   145  
   146  	a.mutex.RLock()
   147  	defer a.mutex.RUnlock()
   148  
   149  	if err, ok := a.errors[GetVpcsAndSubnets]; ok {
   150  		return nil, nil, err
   151  	}
   152  
   153  	vnets := ipamTypes.VirtualNetworkMap{}
   154  	subnets := ipamTypes.SubnetMap{}
   155  
   156  	for _, s := range a.subnets {
   157  		sd := s.subnet.DeepCopy()
   158  		sd.AvailableAddresses = s.allocator.Free()
   159  		subnets[sd.ID] = sd
   160  	}
   161  
   162  	for _, v := range a.vnets {
   163  		vnets[v.ID] = v.DeepCopy()
   164  	}
   165  
   166  	return vnets, subnets, nil
   167  }
   168  
   169  func (a *API) AssignPrivateIpAddressesVM(ctx context.Context, subnetID, interfaceName string, addresses int) error {
   170  	return nil
   171  }
   172  
   173  func (a *API) AssignPrivateIpAddressesVMSS(ctx context.Context, vmName, vmssName, subnetID, interfaceName string, addresses int) error {
   174  	a.rateLimit()
   175  	a.delaySim.Delay(AssignPrivateIpAddressesVMSS)
   176  
   177  	a.mutex.Lock()
   178  	defer a.mutex.Unlock()
   179  
   180  	if err, ok := a.errors[AssignPrivateIpAddressesVMSS]; ok {
   181  		return err
   182  	}
   183  
   184  	foundInterface := false
   185  	instances := a.instances.DeepCopy()
   186  	err := instances.ForeachInterface("", func(id, _ string, iface ipamTypes.InterfaceRevision) error {
   187  		intf, ok := iface.Resource.(*types.AzureInterface)
   188  		if !ok {
   189  			return fmt.Errorf("invalid interface object")
   190  		}
   191  
   192  		if intf.Name != interfaceName || intf.GetVMID() != vmName {
   193  			return nil
   194  		}
   195  
   196  		if len(intf.Addresses)+addresses > types.InterfaceAddressLimit {
   197  			return fmt.Errorf("exceeded interface limit")
   198  		}
   199  
   200  		s, ok := a.subnets[subnetID]
   201  		if !ok {
   202  			return fmt.Errorf("subnet %s does not exist", subnetID)
   203  		}
   204  
   205  		for i := 0; i < addresses; i++ {
   206  			ip, err := s.allocator.AllocateNext()
   207  			if err != nil {
   208  				panic("Unable to allocate IP from allocator")
   209  			}
   210  			intf.Addresses = append(intf.Addresses, types.AzureAddress{
   211  				IP:     ip.String(),
   212  				Subnet: subnetID,
   213  				State:  types.StateSucceeded,
   214  			})
   215  		}
   216  
   217  		foundInterface = true
   218  		return nil
   219  	})
   220  	if err != nil {
   221  		return err
   222  	}
   223  
   224  	a.updateInstancesLocked(instances)
   225  
   226  	if !foundInterface {
   227  		return fmt.Errorf("interface %s not found", interfaceName)
   228  	}
   229  
   230  	return nil
   231  }