Skip to content

Commit 67ae5c8

Browse files
authored
Merge pull request kubernetes#87881 from wojtek-t/limit_add_instances_calls
Limit number of instances in single update to GCE target pool
2 parents 2c0fad1 + 8ec193b commit 67ae5c8

File tree

4 files changed

+99
-6
lines changed

4 files changed

+99
-6
lines changed

staging/src/k8s.io/legacy-cloud-providers/gce/gce.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,9 @@ const (
6868
// AffinityTypeClientIP - affinity based on Client IP.
6969
gceAffinityTypeClientIP = "CLIENT_IP"
7070

71-
operationPollInterval = time.Second
72-
maxTargetPoolCreateInstances = 200
71+
operationPollInterval = time.Second
72+
maxTargetPoolCreateInstances = 200
73+
maxInstancesPerTargetPoolUpdate = 1000
7374

7475
// HTTP Load Balancer parameters
7576
// Configure 8 second period for external health checks.

staging/src/k8s.io/legacy-cloud-providers/gce/gce_loadbalancer_external.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -591,16 +591,32 @@ func (g *Cloud) updateTargetPool(loadBalancerName string, hosts []*gceInstance)
591591
toRemove = append(toRemove, &compute.InstanceReference{Instance: link})
592592
}
593593

594-
if len(toAdd) > 0 {
595-
if err := g.AddInstancesToTargetPool(loadBalancerName, g.region, toAdd); err != nil {
594+
for len(toAdd) > 0 {
595+
// Do not remove more than maxInstancesPerTargetPoolUpdate in a single call.
596+
instancesCount := len(toAdd)
597+
if instancesCount > maxInstancesPerTargetPoolUpdate {
598+
instancesCount = maxInstancesPerTargetPoolUpdate
599+
}
600+
// The operation to add 1000 instances is fairly long (may take minutes), so
601+
// we don't need to worry about saturating QPS limits.
602+
if err := g.AddInstancesToTargetPool(loadBalancerName, g.region, toAdd[:instancesCount]); err != nil {
596603
return err
597604
}
605+
toAdd = toAdd[instancesCount:]
598606
}
599607

600-
if len(toRemove) > 0 {
601-
if err := g.RemoveInstancesFromTargetPool(loadBalancerName, g.region, toRemove); err != nil {
608+
for len(toRemove) > 0 {
609+
// Do not remove more than maxInstancesPerTargetPoolUpdate in a single call.
610+
instancesCount := len(toRemove)
611+
if instancesCount > maxInstancesPerTargetPoolUpdate {
612+
instancesCount = maxInstancesPerTargetPoolUpdate
613+
}
614+
// The operation to remove 1000 instances is fairly long (may take minutes), so
615+
// we don't need to worry about saturating QPS limits.
616+
if err := g.RemoveInstancesFromTargetPool(loadBalancerName, g.region, toRemove[:instancesCount]); err != nil {
602617
return err
603618
}
619+
toRemove = toRemove[instancesCount:]
604620
}
605621

606622
// Try to verify that the correct number of nodes are now in the target pool.

staging/src/k8s.io/legacy-cloud-providers/gce/gce_loadbalancer_external_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,53 @@ func TestForwardingRuleNeedsUpdate(t *testing.T) {
568568
}
569569
}
570570

571+
func TestTargetPoolAddsAndRemoveInstancesInBatches(t *testing.T) {
572+
t.Parallel()
573+
574+
vals := DefaultTestClusterValues()
575+
gce, err := fakeGCECloud(DefaultTestClusterValues())
576+
require.NoError(t, err)
577+
578+
addInstanceCalls := 0
579+
addInstanceHook := func(req *compute.TargetPoolsAddInstanceRequest) {
580+
addInstanceCalls++
581+
}
582+
removeInstanceCalls := 0
583+
removeInstanceHook := func(req *compute.TargetPoolsRemoveInstanceRequest) {
584+
removeInstanceCalls++
585+
}
586+
587+
err = registerTargetPoolAddInstanceHook(gce, addInstanceHook)
588+
assert.NoError(t, err)
589+
err = registerTargetPoolRemoveInstanceHook(gce, removeInstanceHook)
590+
assert.NoError(t, err)
591+
592+
svc := fakeLoadbalancerService("")
593+
nodeName := "default-node"
594+
_, err = createExternalLoadBalancer(gce, svc, []string{nodeName}, vals.ClusterName, vals.ClusterID, vals.ZoneName)
595+
assert.NoError(t, err)
596+
597+
// Insert large number of nodes to test batching.
598+
additionalNodeNames := []string{}
599+
for i := 0; i < 2*maxInstancesPerTargetPoolUpdate+2; i++ {
600+
additionalNodeNames = append(additionalNodeNames, fmt.Sprintf("node-%d", i))
601+
}
602+
allNodes, err := createAndInsertNodes(gce, append([]string{nodeName}, additionalNodeNames...), vals.ZoneName)
603+
assert.NoError(t, err)
604+
err = gce.updateExternalLoadBalancer("", svc, allNodes)
605+
assert.NoError(t, err)
606+
607+
assert.Equal(t, 3, addInstanceCalls)
608+
609+
// Remove large number of nodes to test batching.
610+
allNodes, err = createAndInsertNodes(gce, []string{nodeName}, vals.ZoneName)
611+
assert.NoError(t, err)
612+
err = gce.updateExternalLoadBalancer("", svc, allNodes)
613+
assert.NoError(t, err)
614+
615+
assert.Equal(t, 3, removeInstanceCalls)
616+
}
617+
571618
func TestTargetPoolNeedsRecreation(t *testing.T) {
572619
t.Parallel()
573620

staging/src/k8s.io/legacy-cloud-providers/gce/gce_util.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ limitations under the License.
1919
package gce
2020

2121
import (
22+
"context"
2223
"errors"
2324
"fmt"
2425
"net"
@@ -81,6 +82,34 @@ func fakeGCECloud(vals TestClusterValues) (*Cloud, error) {
8182
return gce, nil
8283
}
8384

85+
func registerTargetPoolAddInstanceHook(gce *Cloud, callback func(*compute.TargetPoolsAddInstanceRequest)) error {
86+
mockGCE, ok := gce.c.(*cloud.MockGCE)
87+
if !ok {
88+
return fmt.Errorf("couldn't cast cloud to mockGCE: %#v", gce)
89+
}
90+
existingHandler := mockGCE.MockTargetPools.AddInstanceHook
91+
hook := func(ctx context.Context, key *meta.Key, req *compute.TargetPoolsAddInstanceRequest, m *cloud.MockTargetPools) error {
92+
callback(req)
93+
return existingHandler(ctx, key, req, m)
94+
}
95+
mockGCE.MockTargetPools.AddInstanceHook = hook
96+
return nil
97+
}
98+
99+
func registerTargetPoolRemoveInstanceHook(gce *Cloud, callback func(*compute.TargetPoolsRemoveInstanceRequest)) error {
100+
mockGCE, ok := gce.c.(*cloud.MockGCE)
101+
if !ok {
102+
return fmt.Errorf("couldn't cast cloud to mockGCE: %#v", gce)
103+
}
104+
existingHandler := mockGCE.MockTargetPools.RemoveInstanceHook
105+
hook := func(ctx context.Context, key *meta.Key, req *compute.TargetPoolsRemoveInstanceRequest, m *cloud.MockTargetPools) error {
106+
callback(req)
107+
return existingHandler(ctx, key, req, m)
108+
}
109+
mockGCE.MockTargetPools.RemoveInstanceHook = hook
110+
return nil
111+
}
112+
84113
type gceInstance struct {
85114
Zone string
86115
Name string

0 commit comments

Comments
 (0)