Skip to content
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

Add support for custom protocol for ELB health checks #3124

Merged
merged 1 commit into from
Mar 1, 2022
Merged
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
1 change: 1 addition & 0 deletions api/v1alpha3/awscluster_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func (r *AWSCluster) ConvertTo(dstRaw conversion.Hub) error {
// Assumes restored and dst are non-nil.
func restoreControlPlaneLoadBalancer(restored, dst *infrav1.AWSLoadBalancerSpec) {
dst.Name = restored.Name
dst.HealthCheckProtocol = restored.HealthCheckProtocol
}

// ConvertFrom converts the v1beta1 AWSCluster receiver to a v1alpha3 AWSCluster.
Expand Down
1 change: 1 addition & 0 deletions api/v1alpha3/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions api/v1alpha4/awscluster_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func (src *AWSCluster) ConvertTo(dstRaw conversion.Hub) error {
// Assumes restored and dst are non-nil.
func restoreControlPlaneLoadBalancer(restored, dst *infrav1.AWSLoadBalancerSpec) {
dst.Name = restored.Name
dst.HealthCheckProtocol = restored.HealthCheckProtocol
}

// ConvertFrom converts the v1beta1 AWSCluster receiver to a v1alpha4 AWSCluster.
Expand Down
1 change: 1 addition & 0 deletions api/v1alpha4/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions api/v1beta1/awscluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ type AWSLoadBalancerSpec struct {
// +optional
Subnets []string `json:"subnets,omitempty"`

// HealthCheckProtocol sets the protocol type for classic ELB health check target
// default value is ClassicELBProtocolSSL
Ankitasw marked this conversation as resolved.
Show resolved Hide resolved
// +optional
HealthCheckProtocol *ClassicELBProtocol `json:"healthCheckProtocol,omitempty"`

// AdditionalSecurityGroups sets the security groups used by the load balancer. Expected to be security group IDs
// This is optional - if not provided new security groups will be created for the load balancer
// +optional
Expand Down
10 changes: 10 additions & 0 deletions api/v1beta1/awscluster_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,16 @@ func (r *AWSCluster) ValidateUpdate(old runtime.Object) error {
r.Spec.ControlPlaneLoadBalancer.Name, "field is immutable"),
)
}

// Block the update for HealthCheckProtocol :
// - if it was not set in old spec but added in new spec
// - if it was set in old spec but changed in new spec
if !reflect.DeepEqual(newLoadBalancer.HealthCheckProtocol, existingLoadBalancer.HealthCheckProtocol) {
allErrs = append(allErrs,
field.Invalid(field.NewPath("spec", "controlPlaneLoadBalancer", "healthCheckProtocol"),
newLoadBalancer.HealthCheckProtocol, "field is immutable once set"),
)
}
}

if !reflect.DeepEqual(oldC.Spec.ControlPlaneEndpoint, clusterv1.APIEndpoint{}) &&
Expand Down
70 changes: 56 additions & 14 deletions api/v1beta1/awscluster_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,16 @@ func TestAWSCluster_ValidateCreate(t *testing.T) {
name string
cluster *AWSCluster
wantErr bool
expect func(t *testing.T, res *AWSLoadBalancerSpec)
expect func(g *WithT, res *AWSLoadBalancerSpec)
}{
// The SSHKeyName tests were moved to sshkeyname_test.go
{
name: "Default nil scheme to `internet-facing`",
cluster: &AWSCluster{
Spec: AWSClusterSpec{},
},
expect: func(t *testing.T, res *AWSLoadBalancerSpec) {
t.Helper()

if res.Scheme.String() != ClassicELBSchemeInternetFacing.String() {
t.Error("Expected internet-facing defaulting for nil loadbalancer schemes")
}
expect: func(g *WithT, res *AWSLoadBalancerSpec) {
g.Expect(res.Scheme.String(), ClassicELBSchemeInternetFacing.String())
},
wantErr: false,
},
Expand All @@ -70,12 +66,8 @@ func TestAWSCluster_ValidateCreate(t *testing.T) {
ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{Scheme: &ClassicELBSchemeIncorrectInternetFacing},
},
},
expect: func(t *testing.T, res *AWSLoadBalancerSpec) {
t.Helper()

if res.Scheme.String() != ClassicELBSchemeInternetFacing.String() {
t.Error("Expected internet-facing defaulting for supported incorrect scheme: Internet-facing")
}
expect: func(g *WithT, res *AWSLoadBalancerSpec) {
g.Expect(res.Scheme.String(), ClassicELBSchemeInternetFacing.String())
},
wantErr: false,
},
Expand Down Expand Up @@ -131,7 +123,7 @@ func TestAWSCluster_ValidateCreate(t *testing.T) {
return err == nil
}, 10*time.Second).Should(Equal(true))

tt.expect(t, c.Spec.ControlPlaneLoadBalancer)
tt.expect(g, c.Spec.ControlPlaneLoadBalancer)
})
}
}
Expand Down Expand Up @@ -368,6 +360,56 @@ func TestAWSCluster_ValidateUpdate(t *testing.T) {
},
wantErr: true,
},
{
name: "Should fail if controlPlaneLoadBalancer healthcheckprotocol is updated",
Ankitasw marked this conversation as resolved.
Show resolved Hide resolved
oldCluster: &AWSCluster{
Spec: AWSClusterSpec{
ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{
HealthCheckProtocol: &ClassicELBProtocolTCP,
},
},
},
newCluster: &AWSCluster{
Spec: AWSClusterSpec{
ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{
HealthCheckProtocol: &ClassicELBProtocolSSL,
},
},
},
wantErr: true,
},
{
name: "Should pass if controlPlaneLoadBalancer healthcheckprotocol is same after update",
oldCluster: &AWSCluster{
Spec: AWSClusterSpec{
ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{
HealthCheckProtocol: &ClassicELBProtocolTCP,
},
},
},
newCluster: &AWSCluster{
Spec: AWSClusterSpec{
ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{
HealthCheckProtocol: &ClassicELBProtocolTCP,
},
},
},
wantErr: false,
},
{
name: "Should fail if controlPlaneLoadBalancer healthcheckprotocol is changed to non-default if it was not set before update",
oldCluster: &AWSCluster{
Spec: AWSClusterSpec{},
},
newCluster: &AWSCluster{
Spec: AWSClusterSpec{
ControlPlaneLoadBalancer: &AWSLoadBalancerSpec{
HealthCheckProtocol: &ClassicELBProtocolTCP,
},
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
4 changes: 4 additions & 0 deletions api/v1beta1/network_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ func (e ClassicELBScheme) String() string {
// ClassicELBProtocol defines listener protocols for a classic load balancer.
type ClassicELBProtocol string

func (e ClassicELBProtocol) String() string {
return string(e)
}

var (
// ClassicELBProtocolTCP defines the ELB API string representing the TCP protocol.
ClassicELBProtocolTCP = ClassicELBProtocol("TCP")
Expand Down
5 changes: 5 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -1651,6 +1651,10 @@ spec:
registered instances in its Availability Zone only. \n Defaults
to false."
type: boolean
healthCheckProtocol:
description: HealthCheckProtocol sets the protocol type for classic
ELB health check target default value is ClassicELBProtocolSSL
type: string
name:
description: Name sets the name of the classic ELB load balancer.
As per AWS, the name must be unique within your set of load
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,11 @@ spec:
registered instances in its Availability Zone only.
\n Defaults to false."
type: boolean
healthCheckProtocol:
description: HealthCheckProtocol sets the protocol type
for classic ELB health check target default value is
ClassicELBProtocolSSL
type: string
name:
description: Name sets the name of the classic ELB load
balancer. As per AWS, the name must be unique within
Expand Down
10 changes: 9 additions & 1 deletion pkg/cloud/services/elb/loadbalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ func (s *Service) getAPIServerClassicELBSpec(elbName string) (*infrav1.ClassicEL
},
},
HealthCheck: &infrav1.ClassicELBHealthCheck{
Target: fmt.Sprintf("%v:%d", infrav1.ClassicELBProtocolSSL, 6443),
Target: fmt.Sprintf("%v:%d", s.getHealthCheckELBProtocol(), 6443),
Interval: 10 * time.Second,
Timeout: 5 * time.Second,
HealthyThreshold: 5,
Expand Down Expand Up @@ -781,6 +781,14 @@ func (s *Service) reconcileELBTags(lb *infrav1.ClassicELB, desiredTags map[strin
return nil
}

func (s *Service) getHealthCheckELBProtocol() *infrav1.ClassicELBProtocol {
Ankitasw marked this conversation as resolved.
Show resolved Hide resolved
controlPlaneELB := s.scope.ControlPlaneLoadBalancer()
if controlPlaneELB != nil && controlPlaneELB.HealthCheckProtocol != nil {
return controlPlaneELB.HealthCheckProtocol
}
return &infrav1.ClassicELBProtocolSSL
}

func fromSDKTypeToClassicELB(v *elb.LoadBalancerDescription, attrs *elb.LoadBalancerAttributes, tags []*elb.Tag) *infrav1.ClassicELB {
res := &infrav1.ClassicELB{
Name: aws.StringValue(v.LoadBalancerName),
Expand Down
36 changes: 30 additions & 6 deletions pkg/cloud/services/elb/loadbalancer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/aws/aws-sdk-go/service/elb"
rgapi "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
"github.com/golang/mock/gomock"
. "github.com/onsi/gomega"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -154,13 +155,13 @@ func TestGetAPIServerClassicELBSpec_ControlPlaneLoadBalancer(t *testing.T) {
name string
lb *infrav1.AWSLoadBalancerSpec
mocks func(m *mock_ec2iface.MockEC2APIMockRecorder)
expect func(t *testing.T, res *infrav1.ClassicELB)
expect func(t *testing.T, g *WithT, res *infrav1.ClassicELB)
}{
{
name: "nil load balancer config",
lb: nil,
mocks: func(m *mock_ec2iface.MockEC2APIMockRecorder) {},
expect: func(t *testing.T, res *infrav1.ClassicELB) {
expect: func(t *testing.T, g *WithT, res *infrav1.ClassicELB) {
t.Helper()
if res.Attributes.CrossZoneLoadBalancing {
t.Error("Expected load balancer not to have cross-zone load balancing enabled")
Expand All @@ -173,7 +174,7 @@ func TestGetAPIServerClassicELBSpec_ControlPlaneLoadBalancer(t *testing.T) {
CrossZoneLoadBalancing: true,
},
mocks: func(m *mock_ec2iface.MockEC2APIMockRecorder) {},
expect: func(t *testing.T, res *infrav1.ClassicELB) {
expect: func(t *testing.T, g *WithT, res *infrav1.ClassicELB) {
t.Helper()
if !res.Attributes.CrossZoneLoadBalancing {
t.Error("Expected load balancer to have cross-zone load balancing enabled")
Expand Down Expand Up @@ -205,7 +206,7 @@ func TestGetAPIServerClassicELBSpec_ControlPlaneLoadBalancer(t *testing.T) {
},
}, nil)
},
expect: func(t *testing.T, res *infrav1.ClassicELB) {
expect: func(t *testing.T, g *WithT, res *infrav1.ClassicELB) {
t.Helper()
if len(res.SubnetIDs) != 2 {
t.Errorf("Expected load balancer to be configured for 2 subnets, got %v", len(res.SubnetIDs))
Expand All @@ -221,17 +222,40 @@ func TestGetAPIServerClassicELBSpec_ControlPlaneLoadBalancer(t *testing.T) {
AdditionalSecurityGroups: []string{"sg-00001", "sg-00002"},
},
mocks: func(m *mock_ec2iface.MockEC2APIMockRecorder) {},
expect: func(t *testing.T, res *infrav1.ClassicELB) {
expect: func(t *testing.T, g *WithT, res *infrav1.ClassicELB) {
t.Helper()
if len(res.SecurityGroupIDs) != 3 {
t.Errorf("Expected load balancer to be configured for 3 security groups, got %v", len(res.SecurityGroupIDs))
}
},
},
{
name: "Should create load balancer spec if elb health check protocol specified in config",
lb: &infrav1.AWSLoadBalancerSpec{
HealthCheckProtocol: &infrav1.ClassicELBProtocolTCP,
},
mocks: func(m *mock_ec2iface.MockEC2APIMockRecorder) {},
expect: func(t *testing.T, g *WithT, res *infrav1.ClassicELB) {
t.Helper()
expectedTarget := fmt.Sprintf("%v:%d", infrav1.ClassicELBProtocolTCP, 6443)
g.Expect(expectedTarget, res.HealthCheck.Target)
},
},
{
name: "Should create load balancer spec with default elb health check protocol",
lb: &infrav1.AWSLoadBalancerSpec{},
mocks: func(m *mock_ec2iface.MockEC2APIMockRecorder) {},
expect: func(t *testing.T, g *WithT, res *infrav1.ClassicELB) {
t.Helper()
expectedTarget := fmt.Sprintf("%v:%d", infrav1.ClassicELBProtocolTCP, 6443)
g.Expect(expectedTarget, res.HealthCheck.Target)
},
},
}
Ankitasw marked this conversation as resolved.
Show resolved Hide resolved

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
g := NewWithT(t)
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
ec2Mock := mock_ec2iface.NewMockEC2API(mockCtrl)
Expand Down Expand Up @@ -270,7 +294,7 @@ func TestGetAPIServerClassicELBSpec_ControlPlaneLoadBalancer(t *testing.T) {
t.Fatal(err)
}

tc.expect(t, spec)
tc.expect(t, g, spec)
})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ resources:
- ../default
patchesStrategicMerge:
- patches/limit-az.yaml
- patches/elb-health-check-protocol.yaml
Ankitasw marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
Ankitasw marked this conversation as resolved.
Show resolved Hide resolved
kind: AWSCluster
metadata:
name: "${CLUSTER_NAME}"
spec:
controlPlaneLoadBalancer:
healthCheckProtocol: "TCP"