Skip to content

Commit 534d85c

Browse files
committed
Added conditions to load balancer services
- Refactor ensureInternalLoadBalancer to return ServiceStatus and update error handling - Changed the return type of ensureInternalLoadBalancer from (*v1.LoadBalancerStatus, error) to (*v1.ServiceStatus, error). - Introduced expectedConditions to manage service status conditions effectively. - Added patch to update conditions in the service
1 parent a4cfad1 commit 534d85c

File tree

10 files changed

+400
-86
lines changed

10 files changed

+400
-86
lines changed

cmd/cloud-controller-manager/main.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
"k8s.io/apimachinery/pkg/util/wait"
2929
cloudprovider "k8s.io/cloud-provider"
3030
"k8s.io/cloud-provider-gcp/providers/gce"
31-
_ "k8s.io/cloud-provider-gcp/providers/gce"
3231
"k8s.io/cloud-provider/app"
3332
"k8s.io/cloud-provider/app/config"
3433
"k8s.io/cloud-provider/names"
@@ -67,6 +66,8 @@ var enableDiscretePortForwarding bool
6766
// LoadBalancerClass
6867
var enableRBSDefaultForL4NetLB bool
6968

69+
var enableLBServiceConditions bool
70+
7071
func main() {
7172
rand.Seed(time.Now().UnixNano())
7273

@@ -85,6 +86,7 @@ func main() {
8586
cloudProviderFS.BoolVar(&enableMultiProject, "enable-multi-project", false, "Enables project selection from Node providerID for GCE API calls. CAUTION: Only enable if Node providerID is configured by a trusted source.")
8687
cloudProviderFS.BoolVar(&enableDiscretePortForwarding, "enable-discrete-port-forwarding", false, "Enables forwarding of individual ports instead of port ranges for GCE external load balancers.")
8788
cloudProviderFS.BoolVar(&enableRBSDefaultForL4NetLB, "enable-rbs-default-l4-netlb", false, "Enables RBS defaulting for GCE L4 NetLB")
89+
cloudProviderFS.BoolVar(&enableLBServiceConditions, "enable-l4lb-svc-conditions", false, "Enables Conditions on L4 LB Service status to reflect provisioned GCP resources.")
8890

8991
// add new controllers and initializers
9092
nodeIpamController := nodeIPAMController{}
@@ -172,5 +174,15 @@ func cloudInitializer(config *config.CompletedConfig) cloudprovider.Interface {
172174
gceCloud.SetEnableRBSDefaultForL4NetLB(true)
173175
}
174176

177+
if enableLBServiceConditions {
178+
gceCloud, ok := (cloud).(*gce.Cloud)
179+
if !ok {
180+
// Fail-fast: If enableLBServiceConditions is set, the cloud
181+
// provider MUST be GCE.
182+
klog.Fatalf("enable-l4lb-svc-conditions requires GCE cloud provider, but got %T", cloud)
183+
}
184+
gceCloud.SetEnableL4LBServiceConditions(true)
185+
}
186+
175187
return cloud
176188
}

providers/gce/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ go_library(
1313
"gce_cert.go",
1414
"gce_clusterid.go",
1515
"gce_clusters.go",
16+
"gce_conditions.go",
1617
"gce_disks.go",
1718
"gce_fake.go",
1819
"gce_firewall.go",
@@ -61,6 +62,8 @@ go_library(
6162
"//vendor/google.golang.org/api/tpu/v1:tpu",
6263
"//vendor/gopkg.in/gcfg.v1:gcfg_v1",
6364
"//vendor/k8s.io/api/core/v1:core",
65+
"//vendor/k8s.io/apimachinery/pkg/api/equality",
66+
"//vendor/k8s.io/apimachinery/pkg/api/meta",
6467
"//vendor/k8s.io/apimachinery/pkg/api/resource",
6568
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:meta",
6669
"//vendor/k8s.io/apimachinery/pkg/fields",

providers/gce/gce.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ import (
3030
"sync"
3131
"time"
3232

33-
gcfg "gopkg.in/gcfg.v1"
34-
3533
"cloud.google.com/go/compute/metadata"
3634
"golang.org/x/oauth2"
3735
"golang.org/x/oauth2/google"
@@ -40,6 +38,7 @@ import (
4038
compute "google.golang.org/api/compute/v1"
4139
container "google.golang.org/api/container/v1"
4240
"google.golang.org/api/option"
41+
gcfg "gopkg.in/gcfg.v1"
4342

4443
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud"
4544

@@ -209,6 +208,9 @@ type Cloud struct {
209208

210209
// enableRBSDefaultForL4NetLB disable Service controller from picking up services by default
211210
enableRBSDefaultForL4NetLB bool
211+
212+
// enableL4LBServiceConditions enable conditions on service status
213+
enableL4LBServiceConditions bool
212214
}
213215

214216
// ConfigGlobal is the in memory representation of the gce.conf config data
@@ -870,6 +872,10 @@ func (g *Cloud) SetEnableRBSDefaultForL4NetLB(enabled bool) {
870872
g.enableRBSDefaultForL4NetLB = enabled
871873
}
872874

875+
func (g *Cloud) SetEnableL4LBServiceConditions(enabled bool) {
876+
g.enableL4LBServiceConditions = enabled
877+
}
878+
873879
// getProjectsBasePath returns the compute API endpoint with the `projects/` element.
874880
// The suffix must be added when generating compute resource urls.
875881
func getProjectsBasePath(basePath string) string {

providers/gce/gce_conditions.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package gce
2+
3+
import (
4+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
5+
)
6+
7+
const (
8+
L4LBBackendServiceConditionType = "ServiceLoadBalancerBackendService"
9+
10+
L4LBTargetPoolConditionType = "ServiceLoadBalancerTargetPool"
11+
12+
L4LBForwardingRuleConditionType = "ServiceLoadBalancerForwardingRule"
13+
L4LBHealthCheckConditionType = "ServiceLoadBalancerHealthCheck"
14+
L4LBFirewallRuleConditionType = "ServiceLoadBalancerFirewallRule"
15+
L4LBFirewallRuleHealthCheckConditionType = "ServiceLoadBalancerFirewallRuleForHealthCheck"
16+
17+
L4LBConditionReason = "GCEResourceAllocated"
18+
)
19+
20+
func NewConditionResourceAllocated(conditionType string, resourceName string) metav1.Condition {
21+
return metav1.Condition{
22+
LastTransitionTime: metav1.Now(),
23+
Type: conditionType,
24+
Status: metav1.ConditionTrue,
25+
Reason: L4LBConditionReason,
26+
Message: resourceName,
27+
}
28+
}
29+
30+
// NewBackendServiceCondition creates a condition for the backend service.
31+
func NewBackendServiceCondition(bsName string) metav1.Condition {
32+
return NewConditionResourceAllocated(L4LBBackendServiceConditionType, bsName)
33+
}
34+
35+
// NewTargetPoolCondition creates a condition for the backend service.
36+
func NewTargetPoolCondition(tpName string) metav1.Condition {
37+
return NewConditionResourceAllocated(L4LBTargetPoolConditionType, tpName)
38+
}
39+
40+
// NewForwardingRuleCondition creates a condition for the TCP forwarding rule.
41+
func NewForwardingRuleCondition(frName string) metav1.Condition {
42+
return NewConditionResourceAllocated(L4LBForwardingRuleConditionType, frName)
43+
}
44+
45+
// NewHealthCheckCondition creates a condition for the health check.
46+
func NewHealthCheckCondition(hcName string) metav1.Condition {
47+
return NewConditionResourceAllocated(L4LBHealthCheckConditionType, hcName)
48+
}
49+
50+
// NewFirewallCondition creates a condition for the firewall.
51+
func NewFirewallCondition(fwName string) metav1.Condition {
52+
return NewConditionResourceAllocated(L4LBFirewallRuleConditionType, fwName)
53+
}
54+
55+
// NewFirewallHealthCheckCondition creates a condition for the firewall health check.
56+
func NewFirewallHealthCheckCondition(fwName string) metav1.Condition {
57+
return NewConditionResourceAllocated(L4LBFirewallRuleHealthCheckConditionType, fwName)
58+
}

providers/gce/gce_loadbalancer.go

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"strings"
2828

2929
v1 "k8s.io/api/core/v1"
30+
"k8s.io/apimachinery/pkg/api/equality"
3031
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3132
corev1apply "k8s.io/client-go/applyconfigurations/core/v1"
3233
metav1apply "k8s.io/client-go/applyconfigurations/meta/v1"
@@ -197,7 +198,7 @@ func (g *Cloud) EnsureLoadBalancer(ctx context.Context, clusterName string, svc
197198
}
198199
}
199200

200-
var status *v1.LoadBalancerStatus
201+
var status *v1.ServiceStatus
201202
switch desiredScheme {
202203
case cloud.SchemeInternal:
203204
status, err = g.ensureInternalLoadBalancer(clusterName, clusterID, svc, existingFwdRule, nodes)
@@ -206,10 +207,17 @@ func (g *Cloud) EnsureLoadBalancer(ctx context.Context, clusterName string, svc
206207
}
207208
if err != nil {
208209
klog.Errorf("Failed to EnsureLoadBalancer(%s, %s, %s, %s, %s), err: %v", clusterName, svc.Namespace, svc.Name, loadBalancerName, g.region, err)
209-
return status, err
210+
return &status.LoadBalancer, err
210211
}
212+
213+
if g.enableL4LBServiceConditions && status != nil && !ConditionsEqual(svc.Status.Conditions, status.Conditions) {
214+
if err := g.patchServiceStatusConditions(ctx, svc, status.Conditions); err != nil {
215+
return &status.LoadBalancer, err
216+
}
217+
}
218+
211219
klog.V(4).Infof("EnsureLoadBalancer(%s, %s, %s, %s, %s): done ensuring loadbalancer.", clusterName, svc.Namespace, svc.Name, loadBalancerName, g.region)
212-
return status, err
220+
return &status.LoadBalancer, err
213221
}
214222

215223
// UpdateLoadBalancer is an implementation of LoadBalancer.UpdateLoadBalancer.
@@ -326,3 +334,65 @@ func hasLoadBalancerPortsError(service *v1.Service) bool {
326334
}
327335
return false
328336
}
337+
338+
// patchServiceStatusConditions uses Server-Side Apply to update the conditions.
339+
func (g *Cloud) patchServiceStatusConditions(ctx context.Context, svc *v1.Service, newConditions []metav1.Condition) error {
340+
if len(newConditions) == 0 {
341+
return nil
342+
}
343+
344+
// Convert []metav1.Condition to standard ApplyConfigurations
345+
var applyConditions []*metav1apply.ConditionApplyConfiguration
346+
for _, c := range newConditions {
347+
applyConditions = append(applyConditions, metav1apply.Condition().
348+
WithType(c.Type).
349+
WithStatus(c.Status).
350+
WithReason(c.Reason).
351+
WithMessage(c.Message).
352+
WithLastTransitionTime(c.LastTransitionTime).
353+
WithObservedGeneration(svc.Generation),
354+
)
355+
}
356+
357+
// Construct the Service ApplyConfiguration.
358+
svcApply := corev1apply.Service(svc.Name, svc.Namespace).
359+
WithStatus(corev1apply.ServiceStatus().
360+
WithConditions(applyConditions...))
361+
362+
opts := metav1.ApplyOptions{
363+
FieldManager: "gce-cloud-controller",
364+
Force: true,
365+
}
366+
367+
klog.V(4).Infof("Patching status conditions for service %s/%s via SSA", svc.Namespace, svc.Name)
368+
_, err := g.client.CoreV1().Services(svc.Namespace).ApplyStatus(ctx, svcApply, opts)
369+
if err != nil {
370+
klog.Errorf("Failed to patch status conditions for service %s/%s: %v", svc.Namespace, svc.Name, err)
371+
}
372+
return err
373+
}
374+
375+
// ConditionsEqual checks if two slices of conditions are semantically equal.
376+
// It ignores order and helps avoid unnecessary patches.
377+
func ConditionsEqual(current, desired []metav1.Condition) bool {
378+
if len(current) != len(desired) {
379+
return false
380+
}
381+
382+
currentMap := make(map[string]metav1.Condition, len(current))
383+
for _, c := range current {
384+
currentMap[c.Type] = c
385+
}
386+
387+
for _, d := range desired {
388+
c, exists := currentMap[d.Type]
389+
if !exists {
390+
return false
391+
}
392+
if !equality.Semantic.DeepEqual(c, d) {
393+
return false
394+
}
395+
}
396+
397+
return true
398+
}

0 commit comments

Comments
 (0)