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 }