Skip to content

Commit 78f631a

Browse files
committed
Add pagination to cloudstack api calls
1 parent 0fdca7b commit 78f631a

4 files changed

Lines changed: 701 additions & 37 deletions

File tree

cloudstack_loadbalancer.go

Lines changed: 107 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -276,13 +276,21 @@ func (cs *CSCloud) UpdateLoadBalancer(ctx context.Context, clusterName string, s
276276
for _, lbRule := range lb.rules {
277277
p := lb.LoadBalancer.NewListLoadBalancerRuleInstancesParams(lbRule.Id)
278278

279-
// Retrieve all VMs currently associated to this load balancer rule.
280-
l, err := lb.LoadBalancer.ListLoadBalancerRuleInstances(p)
279+
// Retrieve all VMs currently associated to this load balancer rule. There
280+
// is one per load balanced node, so this grows with the cluster.
281+
instances, err := listAll(p, func() (int, []*cloudstack.VirtualMachine, error) {
282+
l, err := lb.LoadBalancer.ListLoadBalancerRuleInstances(p)
283+
if err != nil {
284+
return 0, nil, err
285+
}
286+
287+
return l.Count, l.LoadBalancerRuleInstances, nil
288+
})
281289
if err != nil {
282290
return fmt.Errorf("error retrieving associated instances: %v", err)
283291
}
284292

285-
assign, remove := symmetricDifference(lb.hostIDs, l.LoadBalancerRuleInstances)
293+
assign, remove := symmetricDifference(lb.hostIDs, instances)
286294

287295
if len(assign) > 0 {
288296
klog.V(4).Infof("Assigning new hosts (%v) to load balancer rule: %v", assign, lbRule.Name)
@@ -448,12 +456,21 @@ func (cs *CSCloud) getLoadBalancer(service *corev1.Service) (*loadBalancer, erro
448456
p.SetProjectid(cs.projectID)
449457
}
450458

451-
l, err := cs.client.LoadBalancer.ListLoadBalancerRules(p)
459+
// The keyword is matched as a substring server side, so this can return more
460+
// rules than just this service's and has to be paged through.
461+
lbRules, err := listAll(p, func() (int, []*cloudstack.LoadBalancerRule, error) {
462+
l, err := cs.client.LoadBalancer.ListLoadBalancerRules(p)
463+
if err != nil {
464+
return 0, nil, err
465+
}
466+
467+
return l.Count, l.LoadBalancerRules, nil
468+
})
452469
if err != nil {
453470
return nil, fmt.Errorf("error retrieving load balancer rules: %v", err)
454471
}
455472

456-
for _, lbRule := range l.LoadBalancerRules {
473+
for _, lbRule := range lbRules {
457474
lb.rules[lbRule.Name] = lbRule
458475

459476
if lb.ipAddr != "" && lb.ipAddr != lbRule.Publicip {
@@ -507,24 +524,37 @@ func (cs *CSCloud) verifyHosts(nodes []*corev1.Node) ([]string, string, error) {
507524
p.SetProjectid(cs.projectID)
508525
}
509526

510-
l, err := cs.client.VirtualMachine.ListVirtualMachines(p)
527+
vms, err := listAll(p, func() (int, []*cloudstack.VirtualMachine, error) {
528+
l, err := cs.client.VirtualMachine.ListVirtualMachines(p)
529+
if err != nil {
530+
return 0, nil, err
531+
}
532+
533+
return l.Count, l.VirtualMachines, nil
534+
})
511535
if err != nil {
512536
return nil, "", fmt.Errorf("error retrieving list of hosts: %v", err)
513537
}
514538

515539
var hostIDs []string
516540
var networkID string
541+
seen := map[string]bool{}
517542

518543
// Check if the virtual machine is in the hosts slice, then add the corresponding ID.
519-
for _, vm := range l.VirtualMachines {
520-
if hostNames[strings.ToLower(vm.Name)] {
521-
if networkID != "" && networkID != vm.Nic[0].Networkid {
522-
return nil, "", fmt.Errorf("found hosts that belong to different networks")
523-
}
544+
for _, vm := range vms {
545+
// Paging over a set of VMs that is changing underneath us can return
546+
// the same VM on more than one page.
547+
if !hostNames[strings.ToLower(vm.Name)] || seen[vm.Id] {
548+
continue
549+
}
550+
seen[vm.Id] = true
524551

525-
networkID = vm.Nic[0].Networkid
526-
hostIDs = append(hostIDs, vm.Id)
552+
if networkID != "" && networkID != vm.Nic[0].Networkid {
553+
return nil, "", fmt.Errorf("found hosts that belong to different networks")
527554
}
555+
556+
networkID = vm.Nic[0].Networkid
557+
hostIDs = append(hostIDs, vm.Id)
528558
}
529559

530560
if len(hostIDs) == 0 || len(networkID) == 0 {
@@ -807,8 +837,19 @@ func symmetricDifference(hostIDs []string, lbInstances []*cloudstack.VirtualMach
807837
new[hostID] = true
808838
}
809839

840+
// Paging over the instances of a rule can return the same instance twice. A
841+
// duplicate would otherwise be dropped from new on its first occurrence and
842+
// then added to remove on its second, so the same host would be both kept
843+
// and removed.
844+
seen := make(map[string]bool)
845+
810846
var remove []string
811847
for _, instance := range lbInstances {
848+
if seen[instance.Id] {
849+
continue
850+
}
851+
seen[instance.Id] = true
852+
812853
if new[instance.Id] {
813854
delete(new, instance.Id)
814855
continue
@@ -900,6 +941,31 @@ func rulesMapToString(rules map[*cloudstack.FirewallRule]bool) string {
900941
return ls.String()
901942
}
902943

944+
// listFirewallRules retrieves all firewall rules associated with a public IP.
945+
func (lb *loadBalancer) listFirewallRules(publicIpId string) ([]*cloudstack.FirewallRule, error) {
946+
p := lb.Firewall.NewListFirewallRulesParams()
947+
p.SetIpaddressid(publicIpId)
948+
p.SetListall(true)
949+
if lb.projectID != "" {
950+
p.SetProjectid(lb.projectID)
951+
}
952+
953+
klog.V(4).Infof("Listing firewall rules for %v", p)
954+
rules, err := listAll(p, func() (int, []*cloudstack.FirewallRule, error) {
955+
r, err := lb.Firewall.ListFirewallRules(p)
956+
if err != nil {
957+
return 0, nil, err
958+
}
959+
960+
return r.Count, r.FirewallRules, nil
961+
})
962+
if err != nil {
963+
return nil, fmt.Errorf("error fetching firewall rules for public IP %v: %v", publicIpId, err)
964+
}
965+
966+
return rules, nil
967+
}
968+
903969
// updateFirewallRule creates a firewall rule for a load balancer rule
904970
//
905971
// If the rule list is empty, all internet (IPv4: 0.0.0.0/0) is opened for the
@@ -911,23 +977,16 @@ func (lb *loadBalancer) updateFirewallRule(publicIpId string, publicPort int, pr
911977
allowedIPs = []string{defaultAllowedCIDR}
912978
}
913979

914-
p := lb.Firewall.NewListFirewallRulesParams()
915-
p.SetIpaddressid(publicIpId)
916-
p.SetListall(true)
917-
if lb.projectID != "" {
918-
p.SetProjectid(lb.projectID)
919-
}
920-
klog.V(4).Infof("Listing firewall rules for %v", p)
921-
r, err := lb.Firewall.ListFirewallRules(p)
980+
firewallRules, err := lb.listFirewallRules(publicIpId)
922981
if err != nil {
923-
return false, fmt.Errorf("error fetching firewall rules for public IP %v: %v", publicIpId, err)
982+
return false, err
924983
}
925-
klog.V(4).Infof("All firewall rules for %v: %v", lb.ipAddr, rulesToString(r.FirewallRules))
984+
klog.V(4).Infof("All firewall rules for %v: %v", lb.ipAddr, rulesToString(firewallRules))
926985

927986
// find all rules that have a matching proto+port
928987
// a map may or may not be faster, but is a bit easier to understand
929988
filtered := make(map[*cloudstack.FirewallRule]bool)
930-
for _, rule := range r.FirewallRules {
989+
for _, rule := range firewallRules {
931990
if rule.Protocol == protocol.IPProtocol() && rule.Startport == publicPort && rule.Endport == publicPort {
932991
filtered[rule] = true
933992
}
@@ -1003,17 +1062,27 @@ func (lb *loadBalancer) updateNetworkACL(publicPort int, protocol LoadBalancerPr
10031062
networkAclParams := lb.NetworkACL.NewListNetworkACLsParams()
10041063
networkAclParams.SetAclid(network.Aclid)
10051064
networkAclParams.SetNetworkid(networkId)
1065+
networkAclParams.SetListall(true)
1066+
if lb.projectID != "" {
1067+
networkAclParams.SetProjectid(lb.projectID)
1068+
}
10061069

1007-
networkAclResponse, err := lb.NetworkACL.ListNetworkACLs(networkAclParams)
1070+
networkAcls, err := listAll(networkAclParams, func() (int, []*cloudstack.NetworkACL, error) {
1071+
networkAclResponse, err := lb.NetworkACL.ListNetworkACLs(networkAclParams)
1072+
if err != nil {
1073+
return 0, nil, err
1074+
}
10081075

1076+
return networkAclResponse.Count, networkAclResponse.NetworkACLs, nil
1077+
})
10091078
if err != nil {
10101079
return false, fmt.Errorf("error fetching Network ACL with ID: %v for network with id: %v, due to: %s", network.Aclid, networkId, err)
10111080
}
10121081

10131082
// find all network ACL rules that have a matching proto+port
10141083
// a map may or may not be faster, but is a bit easier to understand
10151084
filtered := make(map[*cloudstack.NetworkACL]bool)
1016-
for _, netAclRule := range networkAclResponse.NetworkACLs {
1085+
for _, netAclRule := range networkAcls {
10171086
if netAclRule.Protocol == protocol.IPProtocol() && netAclRule.Startport == strconv.Itoa(publicPort) && netAclRule.Endport == strconv.Itoa(publicPort) {
10181087
filtered[netAclRule] = true
10191088
}
@@ -1045,20 +1114,14 @@ func (lb *loadBalancer) updateNetworkACL(publicPort int, protocol LoadBalancerPr
10451114
//
10461115
// returns true when corresponding rules were deleted
10471116
func (lb *loadBalancer) deleteFirewallRule(publicIpId string, publicPort int, protocol LoadBalancerProtocol) (bool, error) {
1048-
p := lb.Firewall.NewListFirewallRulesParams()
1049-
p.SetIpaddressid(publicIpId)
1050-
p.SetListall(true)
1051-
if lb.projectID != "" {
1052-
p.SetProjectid(lb.projectID)
1053-
}
1054-
r, err := lb.Firewall.ListFirewallRules(p)
1117+
firewallRules, err := lb.listFirewallRules(publicIpId)
10551118
if err != nil {
1056-
return false, fmt.Errorf("error fetching firewall rules for public IP %v: %v", publicIpId, err)
1119+
return false, err
10571120
}
10581121

10591122
// filter by proto:port
10601123
filtered := make([]*cloudstack.FirewallRule, 0, 1)
1061-
for _, rule := range r.FirewallRules {
1124+
for _, rule := range firewallRules {
10621125
if rule.Protocol == protocol.IPProtocol() && rule.Startport == publicPort && rule.Endport == publicPort {
10631126
filtered = append(filtered, rule)
10641127
}
@@ -1088,14 +1151,21 @@ func (lb *loadBalancer) deleteNetworkACLRule(publicPort int, protocol LoadBalanc
10881151
p.SetProjectid(lb.projectID)
10891152
}
10901153

1091-
r, err := lb.NetworkACL.ListNetworkACLs(p)
1154+
networkAcls, err := listAll(p, func() (int, []*cloudstack.NetworkACL, error) {
1155+
r, err := lb.NetworkACL.ListNetworkACLs(p)
1156+
if err != nil {
1157+
return 0, nil, err
1158+
}
1159+
1160+
return r.Count, r.NetworkACLs, nil
1161+
})
10921162
if err != nil {
10931163
return false, fmt.Errorf("error fetching Network ACL rules Network ID %v: %v", networkID, err)
10941164
}
10951165

10961166
// filter by proto:port
10971167
filtered := make([]*cloudstack.NetworkACL, 0, 1)
1098-
for _, rule := range r.NetworkACLs {
1168+
for _, rule := range networkAcls {
10991169
if rule.Protocol == protocol.IPProtocol() && rule.Startport == strconv.Itoa(publicPort) && rule.Endport == strconv.Itoa(publicPort) {
11001170
filtered = append(filtered, rule)
11011171
}

0 commit comments

Comments
 (0)