cloud.go 5.6 KB

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