Implemented proxy protocol support via annotation. Fixes #1
diff --git a/cloudstack_loadbalancer.go b/cloudstack_loadbalancer.go
index 01cb99c..710bfe3 100644
--- a/cloudstack_loadbalancer.go
+++ b/cloudstack_loadbalancer.go
@@ -28,6 +28,13 @@
cloudprovider "k8s.io/cloud-provider"
)
+// ServiceAnnotationLoadBalancerProxyProtocol is the annotation used on the
+// service to enable the proxy protocol on a CloudStack load balancer.
+// This annotation is a boolean value, true means the proxy protocol is enabled.
+// Anything else, including the annotation being absent, disables it.
+// Note that this protocol only applies to TCP service ports.
+const ServiceAnnotationLoadBalancerProxyProtocol = "service.beta.kubernetes.io/cloudstack-load-balancer-proxy-protocol"
+
type loadBalancer struct {
*cloudstack.CloudStackClient
@@ -114,11 +121,17 @@
klog.V(4).Infof("Load balancer %v is associated with IP %v", lb.name, lb.ipAddr)
for _, port := range service.Spec.Ports {
+ // Construct the protocol name first, we need it a few times
+ protocol, err := constructProtocolName(port, service.Annotations)
+ if err != nil {
+ return nil, err
+ }
+
// All ports have their own load balancer rule, so add the port to lbName to keep the names unique.
- lbRuleName := fmt.Sprintf("%s-%d", lb.name, port.Port)
+ lbRuleName := fmt.Sprintf("%s-%s-%d", lb.name, protocol, port.Port)
// If the load balancer rule exists and is up-to-date, we move on to the next rule.
- exists, needsUpdate, err := lb.checkLoadBalancerRule(lbRuleName, port)
+ exists, needsUpdate, err := lb.checkLoadBalancerRule(lbRuleName, port, protocol)
if err != nil {
return nil, err
}
@@ -131,7 +144,7 @@
if needsUpdate {
klog.V(4).Infof("Updating load balancer rule: %v", lbRuleName)
- if err := lb.updateLoadBalancerRule(lbRuleName); err != nil {
+ if err := lb.updateLoadBalancerRule(lbRuleName, protocol); err != nil {
return nil, err
}
// Delete the rule from the map, to prevent it being deleted.
@@ -140,7 +153,7 @@
}
klog.V(4).Infof("Creating load balancer rule: %v", lbRuleName)
- lbRule, err := lb.createLoadBalancerRule(lbRuleName, port)
+ lbRule, err := lb.createLoadBalancerRule(lbRuleName, port, protocol)
if err != nil {
return nil, err
}
@@ -408,9 +421,33 @@
return nil
}
+// constructProtocolName builds a CS API compatible protocol name that incorporates
+// data from a ServicePort and (optionally) annotations on the service.
+// Currently supported are: "tcp", "udp" and "tcp-proxy".
+// The latter two require CloudStack 4.6 or later.
+func constructProtocolName(port v1.ServicePort, annotations map[string]string) (string, error) {
+ proxy := false
+ // FIXME this accepts any value as true, even "false", 0 or other falsey stuff
+ if _, ok := annotations[ServiceAnnotationLoadBalancerProxyProtocol]; ok {
+ proxy = true
+ }
+ switch port.Protocol {
+ case v1.ProtocolTCP:
+ if proxy {
+ return "tcp-proxy", nil
+ } else {
+ return "tcp", nil
+ }
+ case v1.ProtocolUDP:
+ return "udp", nil
+ default:
+ return "", fmt.Errorf("unsupported load balancer protocol: %v", port.Protocol)
+ }
+}
+
// checkLoadBalancerRule checks if the rule already exists and if it does, if it can be updated. If
// it does exist but cannot be updated, it will delete the existing rule so it can be created again.
-func (lb *loadBalancer) checkLoadBalancerRule(lbRuleName string, port v1.ServicePort) (bool, bool, error) {
+func (lb *loadBalancer) checkLoadBalancerRule(lbRuleName string, port v1.ServicePort, protocol string) (bool, bool, error) {
lbRule, ok := lb.rules[lbRuleName]
if !ok {
return false, false, nil
@@ -418,7 +455,9 @@
// Check if any of the values we cannot update (those that require a new load balancer rule) are changed.
if lbRule.Publicip == lb.ipAddr && lbRule.Privateport == strconv.Itoa(int(port.NodePort)) && lbRule.Publicport == strconv.Itoa(int(port.Port)) {
- return true, lbRule.Algorithm != lb.algorithm, nil
+ updateAlgo := lbRule.Algorithm != lb.algorithm
+ updateProto := lbRule.Protocol != protocol
+ return true, updateAlgo || updateProto, nil
}
// Delete the load balancer rule so we can create a new one using the new values.
@@ -430,18 +469,19 @@
}
// updateLoadBalancerRule updates a load balancer rule.
-func (lb *loadBalancer) updateLoadBalancerRule(lbRuleName string) error {
+func (lb *loadBalancer) updateLoadBalancerRule(lbRuleName string, protocol string) error {
lbRule := lb.rules[lbRuleName]
p := lb.LoadBalancer.NewUpdateLoadBalancerRuleParams(lbRule.Id)
p.SetAlgorithm(lb.algorithm)
+ p.SetProtocol(protocol)
_, err := lb.LoadBalancer.UpdateLoadBalancerRule(p)
return err
}
// createLoadBalancerRule creates a new load balancer rule and returns it's ID.
-func (lb *loadBalancer) createLoadBalancerRule(lbRuleName string, port v1.ServicePort) (*cloudstack.LoadBalancerRule, error) {
+func (lb *loadBalancer) createLoadBalancerRule(lbRuleName string, port v1.ServicePort, protocol string) (*cloudstack.LoadBalancerRule, error) {
p := lb.LoadBalancer.NewCreateLoadBalancerRuleParams(
lb.algorithm,
lbRuleName,
@@ -452,14 +492,7 @@
p.SetNetworkid(lb.networkID)
p.SetPublicipid(lb.ipAddrID)
- switch port.Protocol {
- case v1.ProtocolTCP:
- p.SetProtocol("TCP")
- case v1.ProtocolUDP:
- p.SetProtocol("UDP")
- default:
- return nil, fmt.Errorf("unsupported load balancer protocol: %v", port.Protocol)
- }
+ p.SetProtocol(protocol)
// Do not create corresponding firewall rule.
p.SetOpenfirewall(true)