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  }