Skip to content

Add support for HTTP/2 listener protocol in OCI Load Balancer #505

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 28 additions & 10 deletions pkg/cloudprovider/providers/oci/load_balancer_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ const (
// loadbalancer traffic policy("ROUND_ROBIN", "LEAST_CONNECTION", "IP_HASH")
ServiceAnnotationLoadBalancerPolicy = "oci.oraclecloud.com/loadbalancer-policy"

// ServiceAnnotationLoadBalancerProtocol is a service annotation for specifying
// the load balancer listener protocol ("HTTP", "HTTP2", "TCP", "GRPC").
ServiceAnnotationLoadBalancerProtocol = "oci.oraclecloud.com/oci-load-balancer-protocol"

// ServiceAnnotationLoadBalancerInitialDefinedTagsOverride is a service annotation for specifying
// defined tags on the LB
ServiceAnnotationLoadBalancerInitialDefinedTagsOverride = "oci.oraclecloud.com/initial-defined-tags-override"
Expand Down Expand Up @@ -1092,19 +1096,32 @@ func getListenersOciLoadBalancer(svc *v1.Service, sslCfg *SSLConfig) (map[string

listeners := make(map[string]client.GenericListener)
for _, servicePort := range svc.Spec.Ports {
protocol := string(servicePort.Protocol)
// Annotation overrides the protocol.
backendProtocol := string(servicePort.Protocol)
// Backend protocol annotation overrides the protocol.
if p, ok := svc.Annotations[ServiceAnnotationLoadBalancerBEProtocol]; ok {
// Default
if p == "" {
p = DefaultLoadBalancerBEProtocol
}
if strings.EqualFold(p, "HTTP") || strings.EqualFold(p, "TCP") || strings.EqualFold(p, "GRPC") {
protocol = p
backendProtocol = p
} else {
return nil, fmt.Errorf("invalid backend protocol %q requested for load balancer listener. Only 'HTTP', 'TCP' and 'GRPC' protocols supported", p)
}
}

// Listener protocol - starts with backend protocol but can be overridden
listenerProtocol := backendProtocol
if p, ok := svc.Annotations[ServiceAnnotationLoadBalancerProtocol]; ok {
if p != "" {
if strings.EqualFold(p, "HTTP") || strings.EqualFold(p, "HTTP2") || strings.EqualFold(p, "TCP") || strings.EqualFold(p, "GRPC") {
listenerProtocol = p
} else {
return nil, fmt.Errorf("invalid listener protocol %q requested for load balancer listener. Only 'HTTP', 'HTTP2', 'TCP' and 'GRPC' protocols supported", p)
}
}
}

port := int(servicePort.Port)

var secretName string
Expand All @@ -1118,21 +1135,21 @@ func getListenersOciLoadBalancer(svc *v1.Service, sslCfg *SSLConfig) (map[string
return nil, err
}
}
if strings.EqualFold(protocol, "GRPC") {
protocol = ProtocolGrpc
if strings.EqualFold(listenerProtocol, "GRPC") {
listenerProtocol = ProtocolGrpc
if sslConfiguration == nil {
return nil, fmt.Errorf("SSL configuration cannot be empty for GRPC protocol")
}
if sslConfiguration.CipherSuiteName == nil {
sslConfiguration.CipherSuiteName = common.String(DefaultCipherSuiteForGRPC)
}
}
name := getListenerName(protocol, port)
name := getListenerName(listenerProtocol, port)

listener := client.GenericListener{
Name: &name,
DefaultBackendSetName: common.String(getBackendSetName(string(servicePort.Protocol), int(servicePort.Port))),
Copy link
Preview

Copilot AI Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend set name is derived from the original servicePort.Protocol, but if the backend protocol is overridden (e.g., HTTP), the name should reflect the actual backendProtocol. Consider using backendProtocol instead of string(servicePort.Protocol) when calling getBackendSetName.

Suggested change
DefaultBackendSetName: common.String(getBackendSetName(string(servicePort.Protocol), int(servicePort.Port))),
DefaultBackendSetName: common.String(getBackendSetName(listenerProtocol, int(servicePort.Port))),

Copilot uses AI. Check for mistakes.

Protocol: &protocol,
Protocol: &listenerProtocol,
Port: &port,
RuleSetNames: rs,
SslConfiguration: sslConfiguration,
Expand All @@ -1145,10 +1162,11 @@ func getListenersOciLoadBalancer(svc *v1.Service, sslCfg *SSLConfig) (map[string
if proxyProtocolVersion != nil && connectionIdleTimeout == nil {
// At that point LB only supports HTTP and TCP
defaultIdleTimeoutPerProtocol := map[string]int64{
"HTTP": lbConnectionIdleTimeoutHTTP,
"TCP": lbConnectionIdleTimeoutTCP,
"HTTP": lbConnectionIdleTimeoutHTTP,
"HTTP2": lbConnectionIdleTimeoutHTTP, // HTTP/2 uses same timeout as HTTP
"TCP": lbConnectionIdleTimeoutTCP,
}
actualConnectionIdleTimeout = common.Int64(defaultIdleTimeoutPerProtocol[strings.ToUpper(protocol)])
actualConnectionIdleTimeout = common.Int64(defaultIdleTimeoutPerProtocol[strings.ToUpper(listenerProtocol)])
}

if actualConnectionIdleTimeout != nil {
Expand Down
58 changes: 58 additions & 0 deletions pkg/cloudprovider/providers/oci/load_balancer_spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8943,6 +8943,64 @@ func Test_getListeners(t *testing.T) {
},
},
},
{
name: "HTTP2 listener protocol with HTTP backend protocol",
service: &v1.Service{
Spec: v1.ServiceSpec{
Ports: []v1.ServicePort{
{
Protocol: v1.ProtocolTCP,
Port: int32(443),
},
},
},
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
ServiceAnnotationLoadBalancerBEProtocol: "HTTP",
ServiceAnnotationLoadBalancerProtocol: "HTTP2",
},
},
},
listenerBackendIpVersion: []string{IPv4},
sslConfig: nil,
want: map[string]client.GenericListener{
"HTTP-443": {
Name: common.String("HTTP-443"),
Port: common.Int(443),
Protocol: common.String("HTTP2"),
DefaultBackendSetName: common.String("TCP-443"),
},
},
},
{
name: "HTTP listener protocol with HTTP backend protocol",
service: &v1.Service{
Spec: v1.ServiceSpec{
Ports: []v1.ServicePort{
{
Protocol: v1.ProtocolTCP,
Port: int32(80),
},
},
},
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
ServiceAnnotationLoadBalancerBEProtocol: "HTTP",
ServiceAnnotationLoadBalancerProtocol: "HTTP",
},
},
},
listenerBackendIpVersion: []string{IPv4},
sslConfig: nil,
want: map[string]client.GenericListener{
"HTTP-80": {
Name: common.String("HTTP-80"),
Port: common.Int(80),
Protocol: common.String("HTTP"),
DefaultBackendSetName: common.String("TCP-80"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
4 changes: 4 additions & 0 deletions pkg/cloudprovider/providers/oci/load_balancer_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,10 @@ func getSanitizedName(name string) string {
}

func getListenerName(protocol string, port int) string {
// For HTTP and HTTP/2 protocols, always use "HTTP" prefix
if strings.EqualFold(protocol, "HTTP") || strings.EqualFold(protocol, "HTTP2") {
return fmt.Sprintf("HTTP-%d", port)
}
return fmt.Sprintf("%s-%d", protocol, port)
}

Expand Down