k8s.io/kubernetes@v1.29.3/test/integration/ipamperf/cloud.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package ipamperf 18 19 import ( 20 "context" 21 "net" 22 "sync" 23 24 "github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud" 25 "github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta" 26 beta "google.golang.org/api/compute/v0.beta" 27 ga "google.golang.org/api/compute/v1" 28 "k8s.io/kubernetes/pkg/controller/nodeipam/ipam/cidrset" 29 "k8s.io/kubernetes/test/integration/util" 30 ) 31 32 // implemntation note: 33 // ------------------ 34 // cloud.go implements hooks and handler functions for the MockGCE cloud in order to meet expectations 35 // of cloud behavior from the IPAM controllers. The key constraint is that the IPAM code is spread 36 // across both GA and Beta instances, which are distinct objects in the mock. We need to solve for 37 // 38 // 1. When a GET is called on an instance, we lazy create the instance with or without an assigned 39 // ip alias as needed by the IPAM controller type 40 // 2. When we assign an IP alias for an instance, both the GA and Beta instance have to agree on the 41 // assigned alias range 42 // 43 // We solve both the problems by using a baseInstanceList which maintains a list of known instances, 44 // and their pre-assigned ip-alias ranges (if needed). We then create GetHook for GA and Beta GetInstance 45 // calls as closures over this betaInstanceList that can lookup base instance data. 46 // 47 // This has the advantage that once the Get hook pouplates the GCEMock with the base data, we then let the 48 // rest of the mock code run as is. 49 50 // baseInstance tracks basic instance data needed by the IPAM controllers 51 type baseInstance struct { 52 name string 53 zone string 54 aliasRange string 55 } 56 57 // baseInstanceList tracks a set of base instances 58 type baseInstanceList struct { 59 allocateCIDR bool 60 clusterCIDR *net.IPNet 61 subnetMaskSize int 62 cidrSet *cidrset.CidrSet 63 64 lock sync.Mutex // protect access to instances 65 instances map[meta.Key]*baseInstance 66 } 67 68 // toGA is an utility method to return the baseInstance data as a GA Instance object 69 func (bi *baseInstance) toGA() *ga.Instance { 70 inst := &ga.Instance{Name: bi.name, Zone: bi.zone, NetworkInterfaces: []*ga.NetworkInterface{{}}} 71 if bi.aliasRange != "" { 72 inst.NetworkInterfaces[0].AliasIpRanges = []*ga.AliasIpRange{ 73 {IpCidrRange: bi.aliasRange, SubnetworkRangeName: util.TestSecondaryRangeName}, 74 } 75 } 76 return inst 77 } 78 79 // toGA is an utility method to return the baseInstance data as a beta Instance object 80 func (bi *baseInstance) toBeta() *beta.Instance { 81 inst := &beta.Instance{Name: bi.name, Zone: bi.zone, NetworkInterfaces: []*beta.NetworkInterface{{}}} 82 if bi.aliasRange != "" { 83 inst.NetworkInterfaces[0].AliasIpRanges = []*beta.AliasIpRange{ 84 {IpCidrRange: bi.aliasRange, SubnetworkRangeName: util.TestSecondaryRangeName}, 85 } 86 } 87 return inst 88 } 89 90 // newBaseInstanceList is the baseInstanceList constructor 91 func newBaseInstanceList(allocateCIDR bool, clusterCIDR *net.IPNet, subnetMaskSize int) *baseInstanceList { 92 cidrSet, _ := cidrset.NewCIDRSet(clusterCIDR, subnetMaskSize) 93 return &baseInstanceList{ 94 allocateCIDR: allocateCIDR, 95 clusterCIDR: clusterCIDR, 96 subnetMaskSize: subnetMaskSize, 97 cidrSet: cidrSet, 98 instances: make(map[meta.Key]*baseInstance), 99 } 100 } 101 102 // getOrCreateBaseInstance lazily creates a new base instance, assigning if allocateCIDR is true 103 func (bil *baseInstanceList) getOrCreateBaseInstance(key *meta.Key) *baseInstance { 104 bil.lock.Lock() 105 defer bil.lock.Unlock() 106 107 inst, found := bil.instances[*key] 108 if !found { 109 inst = &baseInstance{name: key.Name, zone: key.Zone} 110 if bil.allocateCIDR { 111 nextRange, _ := bil.cidrSet.AllocateNext() 112 inst.aliasRange = nextRange.String() 113 } 114 bil.instances[*key] = inst 115 } 116 return inst 117 } 118 119 // newGAGetHook creates a new closure with the current baseInstanceList to be used as a MockInstances.GetHook 120 func (bil *baseInstanceList) newGAGetHook() func(ctx context.Context, key *meta.Key, m *cloud.MockInstances) (bool, *ga.Instance, error) { 121 return func(ctx context.Context, key *meta.Key, m *cloud.MockInstances) (bool, *ga.Instance, error) { 122 m.Lock.Lock() 123 defer m.Lock.Unlock() 124 125 if _, found := m.Objects[*key]; !found { 126 m.Objects[*key] = &cloud.MockInstancesObj{Obj: bil.getOrCreateBaseInstance(key).toGA()} 127 } 128 return false, nil, nil 129 } 130 } 131 132 // newBetaGetHook creates a new closure with the current baseInstanceList to be used as a MockBetaInstances.GetHook 133 func (bil *baseInstanceList) newBetaGetHook() func(ctx context.Context, key *meta.Key, m *cloud.MockBetaInstances) (bool, *beta.Instance, error) { 134 return func(ctx context.Context, key *meta.Key, m *cloud.MockBetaInstances) (bool, *beta.Instance, error) { 135 m.Lock.Lock() 136 defer m.Lock.Unlock() 137 138 if _, found := m.Objects[*key]; !found { 139 m.Objects[*key] = &cloud.MockInstancesObj{Obj: bil.getOrCreateBaseInstance(key).toBeta()} 140 } 141 return false, nil, nil 142 } 143 } 144 145 // newMockCloud returns a mock GCE instance with the appropriate handlers hooks 146 func (bil *baseInstanceList) newMockCloud() cloud.Cloud { 147 c := cloud.NewMockGCE(nil) 148 149 // insert hooks to lazy create a instance when needed 150 c.MockInstances.GetHook = bil.newGAGetHook() 151 c.MockBetaInstances.GetHook = bil.newBetaGetHook() 152 153 return c 154 }