func validateLoadBalancer(t *testing.T, loadBalancer network.LoadBalancer, services ...api.Service) { expectedRuleCount := 0 for _, svc := range services { for _, wantedRule := range svc.Spec.Ports { expectedRuleCount++ wantedRuleName := getRuleName(&svc, wantedRule) foundRule := false for _, actualRule := range *loadBalancer.Properties.LoadBalancingRules { if strings.EqualFold(*actualRule.Name, wantedRuleName) && *actualRule.Properties.FrontendPort == wantedRule.Port && *actualRule.Properties.BackendPort == wantedRule.Port { foundRule = true break } } if !foundRule { t.Errorf("Expected load balancer rule but didn't find it: %q", wantedRuleName) } foundProbe := false if serviceapi.NeedsHealthCheck(&svc) { path, port := serviceapi.GetServiceHealthCheckPathPort(&svc) for _, actualProbe := range *loadBalancer.Properties.Probes { if strings.EqualFold(*actualProbe.Name, wantedRuleName) && *actualProbe.Properties.Port == port && *actualProbe.Properties.RequestPath == path && actualProbe.Properties.Protocol == network.ProbeProtocolHTTP { foundProbe = true break } } } else { for _, actualProbe := range *loadBalancer.Properties.Probes { if strings.EqualFold(*actualProbe.Name, wantedRuleName) && *actualProbe.Properties.Port == wantedRule.NodePort { foundProbe = true break } } } if !foundProbe { for _, actualProbe := range *loadBalancer.Properties.Probes { t.Logf("Probe: %s %d", *actualProbe.Name, *actualProbe.Properties.Port) } t.Errorf("Expected loadbalancer probe but didn't find it: %q", wantedRuleName) } } } lenRules := len(*loadBalancer.Properties.LoadBalancingRules) if lenRules != expectedRuleCount { t.Errorf("Expected the loadbalancer to have %d rules. Found %d.", expectedRuleCount, lenRules) } lenProbes := len(*loadBalancer.Properties.Probes) if lenProbes != expectedRuleCount { t.Errorf("Expected the loadbalancer to have %d probes. Found %d.", expectedRuleCount, lenProbes) } }
// This ensures load balancer exists and the frontend ip config is setup. // This also reconciles the Service's Ports with the LoadBalancer config. // This entails adding rules/probes for expected Ports and removing stale rules/ports. func (az *Cloud) reconcileLoadBalancer(lb network.LoadBalancer, pip *network.PublicIPAddress, clusterName string, service *api.Service, nodeNames []string) (network.LoadBalancer, bool, error) { lbName := getLoadBalancerName(clusterName) serviceName := getServiceName(service) lbFrontendIPConfigName := getFrontendIPConfigName(service) lbFrontendIPConfigID := az.getFrontendIPConfigID(lbName, lbFrontendIPConfigName) lbBackendPoolName := getBackendPoolName(clusterName) lbBackendPoolID := az.getBackendPoolID(lbName, lbBackendPoolName) wantLb := len(service.Spec.Ports) > 0 dirtyLb := false // Ensure LoadBalancer's Backend Pool Configuration if wantLb { if lb.Properties.BackendAddressPools == nil || len(*lb.Properties.BackendAddressPools) == 0 { lb.Properties.BackendAddressPools = &[]network.BackendAddressPool{ { Name: to.StringPtr(lbBackendPoolName), }, } glog.V(10).Infof("reconcile(%s)(%t): lb backendpool - adding", serviceName, wantLb) dirtyLb = true } else if len(*lb.Properties.BackendAddressPools) != 1 || !strings.EqualFold(*(*lb.Properties.BackendAddressPools)[0].Name, lbBackendPoolName) { return lb, false, fmt.Errorf("loadbalancer is misconfigured with a different backend pool") } } // Ensure LoadBalancer's Frontend IP Configurations dirtyConfigs := false newConfigs := []network.FrontendIPConfiguration{} if lb.Properties.FrontendIPConfigurations != nil { newConfigs = *lb.Properties.FrontendIPConfigurations } if !wantLb { for i := len(newConfigs) - 1; i >= 0; i-- { config := newConfigs[i] if strings.EqualFold(*config.Name, lbFrontendIPConfigName) { glog.V(3).Infof("reconcile(%s)(%t): lb frontendconfig(%s) - dropping", serviceName, wantLb, lbFrontendIPConfigName) newConfigs = append(newConfigs[:i], newConfigs[i+1:]...) dirtyConfigs = true } } } else { foundConfig := false for _, config := range newConfigs { if strings.EqualFold(*config.Name, lbFrontendIPConfigName) { foundConfig = true break } } if !foundConfig { newConfigs = append(newConfigs, network.FrontendIPConfiguration{ Name: to.StringPtr(lbFrontendIPConfigName), Properties: &network.FrontendIPConfigurationPropertiesFormat{ PublicIPAddress: &network.PublicIPAddress{ ID: pip.ID, }, }, }) glog.V(10).Infof("reconcile(%s)(%t): lb frontendconfig(%s) - adding", serviceName, wantLb, lbFrontendIPConfigName) dirtyConfigs = true } } if dirtyConfigs { dirtyLb = true lb.Properties.FrontendIPConfigurations = &newConfigs } // update probes/rules expectedProbes := make([]network.Probe, len(service.Spec.Ports)) expectedRules := make([]network.LoadBalancingRule, len(service.Spec.Ports)) for i, port := range service.Spec.Ports { lbRuleName := getRuleName(service, port) transportProto, _, probeProto, err := getProtocolsFromKubernetesProtocol(port.Protocol) if err != nil { return lb, false, err } if serviceapi.NeedsHealthCheck(service) { podPresencePath, podPresencePort := serviceapi.GetServiceHealthCheckPathPort(service) expectedProbes[i] = network.Probe{ Name: &lbRuleName, Properties: &network.ProbePropertiesFormat{ RequestPath: to.StringPtr(podPresencePath), Protocol: network.ProbeProtocolHTTP, Port: to.Int32Ptr(podPresencePort), IntervalInSeconds: to.Int32Ptr(5), NumberOfProbes: to.Int32Ptr(2), }, } } else { expectedProbes[i] = network.Probe{ Name: &lbRuleName, Properties: &network.ProbePropertiesFormat{ Protocol: probeProto, Port: to.Int32Ptr(port.NodePort), IntervalInSeconds: to.Int32Ptr(5), NumberOfProbes: to.Int32Ptr(2), }, } } expectedRules[i] = network.LoadBalancingRule{ Name: &lbRuleName, Properties: &network.LoadBalancingRulePropertiesFormat{ Protocol: transportProto, FrontendIPConfiguration: &network.SubResource{ ID: to.StringPtr(lbFrontendIPConfigID), }, BackendAddressPool: &network.SubResource{ ID: to.StringPtr(lbBackendPoolID), }, Probe: &network.SubResource{ ID: to.StringPtr(az.getLoadBalancerProbeID(lbName, lbRuleName)), }, FrontendPort: to.Int32Ptr(port.Port), BackendPort: to.Int32Ptr(port.Port), EnableFloatingIP: to.BoolPtr(true), }, } } // remove unwanted probes dirtyProbes := false var updatedProbes []network.Probe if lb.Properties.Probes != nil { updatedProbes = *lb.Properties.Probes } for i := len(updatedProbes) - 1; i >= 0; i-- { existingProbe := updatedProbes[i] if serviceOwnsRule(service, *existingProbe.Name) { glog.V(10).Infof("reconcile(%s)(%t): lb probe(%s) - considering evicting", serviceName, wantLb, *existingProbe.Name) keepProbe := false if findProbe(expectedProbes, existingProbe) { glog.V(10).Infof("reconcile(%s)(%t): lb probe(%s) - keeping", serviceName, wantLb, *existingProbe.Name) keepProbe = true } if !keepProbe { updatedProbes = append(updatedProbes[:i], updatedProbes[i+1:]...) glog.V(10).Infof("reconcile(%s)(%t): lb probe(%s) - dropping", serviceName, wantLb, *existingProbe.Name) dirtyProbes = true } } } // add missing, wanted probes for _, expectedProbe := range expectedProbes { foundProbe := false if findProbe(updatedProbes, expectedProbe) { glog.V(10).Infof("reconcile(%s)(%t): lb probe(%s) - already exists", serviceName, wantLb, *expectedProbe.Name) foundProbe = true } if !foundProbe { glog.V(10).Infof("reconcile(%s)(%t): lb probe(%s) - adding", serviceName, wantLb, *expectedProbe.Name) updatedProbes = append(updatedProbes, expectedProbe) dirtyProbes = true } } if dirtyProbes { dirtyLb = true lb.Properties.Probes = &updatedProbes } // update rules dirtyRules := false var updatedRules []network.LoadBalancingRule if lb.Properties.LoadBalancingRules != nil { updatedRules = *lb.Properties.LoadBalancingRules } // update rules: remove unwanted for i := len(updatedRules) - 1; i >= 0; i-- { existingRule := updatedRules[i] if serviceOwnsRule(service, *existingRule.Name) { keepRule := false glog.V(10).Infof("reconcile(%s)(%t): lb rule(%s) - considering evicting", serviceName, wantLb, *existingRule.Name) if findRule(expectedRules, existingRule) { glog.V(10).Infof("reconcile(%s)(%t): lb rule(%s) - keeping", serviceName, wantLb, *existingRule.Name) keepRule = true } if !keepRule { glog.V(3).Infof("reconcile(%s)(%t): lb rule(%s) - dropping", serviceName, wantLb, *existingRule.Name) updatedRules = append(updatedRules[:i], updatedRules[i+1:]...) dirtyRules = true } } } // update rules: add needed for _, expectedRule := range expectedRules { foundRule := false if findRule(updatedRules, expectedRule) { glog.V(10).Infof("reconcile(%s)(%t): lb rule(%s) - already exists", serviceName, wantLb, *expectedRule.Name) foundRule = true } if !foundRule { glog.V(10).Infof("reconcile(%s)(%t): lb rule(%s) adding", serviceName, wantLb, *expectedRule.Name) updatedRules = append(updatedRules, expectedRule) dirtyRules = true } } if dirtyRules { dirtyLb = true lb.Properties.LoadBalancingRules = &updatedRules } return lb, dirtyLb, nil }