Skip to content

Commit 6950c52

Browse files
committed
Implement MAPI2CAPI conversion of loadbalancers
1 parent 82bcb80 commit 6950c52

File tree

18 files changed

+992
-100
lines changed

18 files changed

+992
-100
lines changed

pkg/controllers/infracluster/aws.go

Lines changed: 109 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,34 +17,45 @@ package infracluster
1717

1818
import (
1919
"context"
20+
"errors"
2021
"fmt"
2122
"net/url"
2223
"strconv"
24+
"strings"
2325

2426
"github.com/go-logr/logr"
2527
cerrors "k8s.io/apimachinery/pkg/api/errors"
2628
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2729
awsv1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
2830
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
2931
"sigs.k8s.io/controller-runtime/pkg/client"
32+
33+
mapiv1beta1 "github.com/openshift/api/machine/v1beta1"
34+
"sigs.k8s.io/yaml"
35+
)
36+
37+
var (
38+
// ErrNoControlPlaneLoadBalancerConfigured indicates there is no control plane load balancer configuration present.
39+
ErrNoControlPlaneLoadBalancerConfigured = errors.New("no control plane load balancer configured")
40+
// ErrNilProviderSpec indicates a nil provider spec raw extension.
41+
ErrNilProviderSpec = errors.New("provider spec is nil")
3042
)
3143

3244
// ensureAWSCluster ensures the AWSCluster cluster object exists.
3345
func (r *InfraClusterController) ensureAWSCluster(ctx context.Context, log logr.Logger) (client.Object, error) {
34-
target := &awsv1.AWSCluster{ObjectMeta: metav1.ObjectMeta{
46+
awsCluster := &awsv1.AWSCluster{ObjectMeta: metav1.ObjectMeta{
3547
Name: r.Infra.Status.InfrastructureName,
36-
Namespace: defaultCAPINamespace,
48+
Namespace: r.CAPINamespace,
3749
}}
3850

3951
// Checking whether InfraCluster object exists. If it doesn't, create it.
40-
41-
if err := r.Get(ctx, client.ObjectKeyFromObject(target), target); err != nil && !cerrors.IsNotFound(err) {
52+
if err := r.Get(ctx, client.ObjectKeyFromObject(awsCluster), awsCluster); err != nil && !cerrors.IsNotFound(err) {
4253
return nil, fmt.Errorf("failed to get InfraCluster: %w", err)
4354
} else if err == nil {
44-
return target, nil
55+
return awsCluster, nil
4556
}
4657

47-
log.Info(fmt.Sprintf("AWSCluster %s/%s does not exist, creating it", target.Namespace, target.Name))
58+
log.Info(fmt.Sprintf("AWSCluster %s/%s does not exist, creating it", awsCluster.Namespace, awsCluster.Name))
4859

4960
apiURL, err := url.Parse(r.Infra.Status.APIServerInternalURL)
5061
if err != nil {
@@ -60,10 +71,35 @@ func (r *InfraClusterController) ensureAWSCluster(ctx context.Context, log logr.
6071
return nil, fmt.Errorf("infrastructure PlatformStatus should not be nil: %w", err)
6172
}
6273

63-
target = &awsv1.AWSCluster{
74+
providerSpec, err := r.getAWSMAPIProviderSpec(ctx, r.Client)
75+
if err != nil {
76+
return nil, fmt.Errorf("unable to obtain MAPI ProviderSpec: %w", err)
77+
}
78+
79+
awsCluster, err = r.newAWSCluster(providerSpec, apiURL, port)
80+
if err != nil {
81+
return nil, fmt.Errorf("failed to get AWSCluster: %w", err)
82+
}
83+
84+
if err := r.Create(ctx, awsCluster); err != nil {
85+
return nil, fmt.Errorf("failed to create AWSCluster: %w", err)
86+
}
87+
88+
log.Info(fmt.Sprintf("InfraCluster '%s/%s' successfully created", r.CAPINamespace, r.Infra.Status.InfrastructureName))
89+
90+
return awsCluster, nil
91+
}
92+
93+
func (r *InfraClusterController) newAWSCluster(providerSpec *mapiv1beta1.AWSMachineProviderConfig, apiURL *url.URL, port int64) (*awsv1.AWSCluster, error) {
94+
controlPlaneLoadBalancer, secondaryControlPlaneLoadBalancer, err := extractLoadBalancerConfigFromMAPIProviderSpec(providerSpec)
95+
if err != nil {
96+
return nil, fmt.Errorf("failed to extract control plane load balancer configuration: %w", err)
97+
}
98+
99+
target := &awsv1.AWSCluster{
64100
ObjectMeta: metav1.ObjectMeta{
65101
Name: r.Infra.Status.InfrastructureName,
66-
Namespace: defaultCAPINamespace,
102+
Namespace: r.CAPINamespace,
67103
// The ManagedBy Annotation is set so CAPI infra providers ignore the InfraCluster object,
68104
// as that's managed externally, in this case by this controller.
69105
Annotations: map[string]string{
@@ -84,14 +120,75 @@ func (r *InfraClusterController) ensureAWSCluster(ctx context.Context, log logr.
84120
Kind: awsv1.ControllerIdentityKind,
85121
Name: "default",
86122
},
123+
// Set control plane load balancer configuration extracted from MAPI machines
124+
ControlPlaneLoadBalancer: controlPlaneLoadBalancer,
125+
SecondaryControlPlaneLoadBalancer: secondaryControlPlaneLoadBalancer,
87126
},
88127
}
89128

90-
if err := r.Create(ctx, target); err != nil {
91-
return nil, fmt.Errorf("failed to create InfraCluster: %w", err)
129+
return target, nil
130+
}
131+
132+
func (r *InfraClusterController) getAWSMAPIProviderSpec(ctx context.Context, cl client.Client) (*mapiv1beta1.AWSMachineProviderConfig, error) {
133+
rawProviderSpec, err := r.getRawMAPIProviderSpec(ctx, cl)
134+
if err != nil {
135+
return nil, fmt.Errorf("unable to obtain MAPI ProviderSpec: %w", err)
92136
}
93137

94-
log.Info(fmt.Sprintf("InfraCluster '%s/%s' successfully created", defaultCAPINamespace, r.Infra.Status.InfrastructureName))
138+
providerSpec := &mapiv1beta1.AWSMachineProviderConfig{}
139+
if err := yaml.Unmarshal(rawProviderSpec, providerSpec); err != nil {
140+
return nil, fmt.Errorf("unable to unmarshal MAPI ProviderSpec: %w", err)
141+
}
95142

96-
return target, nil
143+
return providerSpec, nil
144+
}
145+
146+
// extractLoadBalancerConfigFromMAPIProviderSpec extracts 1-2 control plane load balancers from a MAPI machine's provider spec.
147+
// The first load balancer is treated as the internal load balancer, and the second (if present) as the secondary one.
148+
// When two load balancers are present, prefer the one whose name ends with "-int" as the first return value.
149+
// Fails if fewer than 1 or more than 2 load balancers are defined.
150+
func extractLoadBalancerConfigFromMAPIProviderSpec(providerSpec *mapiv1beta1.AWSMachineProviderConfig) (*awsv1.AWSLoadBalancerSpec, *awsv1.AWSLoadBalancerSpec, error) {
151+
if providerSpec == nil {
152+
return nil, nil, ErrNilProviderSpec
153+
}
154+
155+
switch len(providerSpec.LoadBalancers) {
156+
case 0:
157+
return nil, nil, ErrNoControlPlaneLoadBalancerConfigured
158+
case 1:
159+
lbPrimary := providerSpec.LoadBalancers[0]
160+
return &awsv1.AWSLoadBalancerSpec{
161+
Name: &lbPrimary.Name,
162+
LoadBalancerType: convertMAPILoadBalancerTypeToCAPI(lbPrimary.Type),
163+
}, nil, nil
164+
case 2:
165+
lbFirst := providerSpec.LoadBalancers[0]
166+
lbSecond := providerSpec.LoadBalancers[1]
167+
// Prefer the load balancer with "-int" suffix as primary when two are present.
168+
if strings.HasSuffix(lbSecond.Name, "-int") && !strings.HasSuffix(lbFirst.Name, "-int") {
169+
lbFirst, lbSecond = lbSecond, lbFirst
170+
}
171+
return &awsv1.AWSLoadBalancerSpec{
172+
Name: &lbFirst.Name,
173+
LoadBalancerType: convertMAPILoadBalancerTypeToCAPI(lbFirst.Type),
174+
}, &awsv1.AWSLoadBalancerSpec{
175+
Name: &lbSecond.Name,
176+
LoadBalancerType: convertMAPILoadBalancerTypeToCAPI(lbSecond.Type),
177+
}, nil
178+
default:
179+
return nil, nil, fmt.Errorf("invalid number of control plane load balancers: expected 1 or 2, got %d", len(providerSpec.LoadBalancers))
180+
}
181+
}
182+
183+
// convertMAPILoadBalancerTypeToCAPI converts MAPI AWSLoadBalancerType to CAPI LoadBalancerType.
184+
func convertMAPILoadBalancerTypeToCAPI(mapiType mapiv1beta1.AWSLoadBalancerType) awsv1.LoadBalancerType {
185+
switch mapiType {
186+
case mapiv1beta1.ClassicLoadBalancerType:
187+
return awsv1.LoadBalancerTypeClassic
188+
case mapiv1beta1.NetworkLoadBalancerType:
189+
return awsv1.LoadBalancerTypeNLB
190+
default:
191+
// Default to classic for unknown types
192+
return awsv1.LoadBalancerTypeClassic
193+
}
97194
}

pkg/controllers/infracluster/azure.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func (r *InfraClusterController) ensureAzureCluster(ctx context.Context, log log
5151
// Get the Azure Bootstrap Credentials Secret.
5252
// This is created by the Cluster Credential Operator and should always exist. We expect to always find it, if not, we error.
5353
capzManagerBootstrapSecret := &corev1.Secret{}
54-
if err := r.Get(ctx, client.ObjectKey{Namespace: defaultCAPINamespace, Name: capzManagerBootstrapCredentials}, capzManagerBootstrapSecret); err != nil && !cerrors.IsNotFound(err) {
54+
if err := r.Get(ctx, client.ObjectKey{Namespace: r.CAPINamespace, Name: capzManagerBootstrapCredentials}, capzManagerBootstrapSecret); err != nil && !cerrors.IsNotFound(err) {
5555
return nil, fmt.Errorf("failed to get Azure Boostrap Credentials Secret: %w", err)
5656
}
5757

@@ -65,7 +65,7 @@ func (r *InfraClusterController) ensureAzureCluster(ctx context.Context, log log
6565

6666
target := &azurev1.AzureCluster{ObjectMeta: metav1.ObjectMeta{
6767
Name: r.Infra.Status.InfrastructureName,
68-
Namespace: defaultCAPINamespace,
68+
Namespace: r.CAPINamespace,
6969
}}
7070

7171
if err := r.ensureAzureInfraCluster(ctx, target, log); err != nil {
@@ -76,8 +76,8 @@ func (r *InfraClusterController) ensureAzureCluster(ctx context.Context, log log
7676
}
7777

7878
// getAzureMAPIProviderSpec returns a Azure Machine ProviderSpec from the the cluster.
79-
func getAzureMAPIProviderSpec(ctx context.Context, cl client.Client) (*mapiv1beta1.AzureMachineProviderSpec, error) {
80-
rawProviderSpec, err := getRawMAPIProviderSpec(ctx, cl)
79+
func (r *InfraClusterController) getAzureMAPIProviderSpec(ctx context.Context, cl client.Client) (*mapiv1beta1.AzureMachineProviderSpec, error) {
80+
rawProviderSpec, err := r.getRawMAPIProviderSpec(ctx, cl)
8181
if err != nil {
8282
return nil, fmt.Errorf("unable to obtain MAPI ProviderSpec: %w", err)
8383
}
@@ -93,7 +93,7 @@ func getAzureMAPIProviderSpec(ctx context.Context, cl client.Client) (*mapiv1bet
9393
// getAzureLocation obtains the Azure Location.
9494
func (r *InfraClusterController) getAzureLocation(ctx context.Context) (string, error) {
9595
// Devise Azure location via MAPI providerSpec.
96-
machineSpec, err := getAzureMAPIProviderSpec(ctx, r.Client)
96+
machineSpec, err := r.getAzureMAPIProviderSpec(ctx, r.Client)
9797
if err != nil {
9898
return "", fmt.Errorf("error getting azure providerSpec: %w", err)
9999
}
@@ -111,26 +111,26 @@ func (r *InfraClusterController) ensureClusterSecret(ctx context.Context, capzMa
111111
// CAPZ controllers expect a Secret Ref with a different data structure to what the secret we get from the Cluster Credential Operator above provides.
112112
// That is why we are creating a new secret below to copy the values to.
113113
clusterSecret := &corev1.Secret{}
114-
if err := r.Get(ctx, client.ObjectKey{Namespace: defaultCAPINamespace, Name: clusterSecretName}, clusterSecret); err != nil && !cerrors.IsNotFound(err) {
114+
if err := r.Get(ctx, client.ObjectKey{Namespace: r.CAPINamespace, Name: clusterSecretName}, clusterSecret); err != nil && !cerrors.IsNotFound(err) {
115115
return fmt.Errorf("failed to get Azure Cluster Secret: %w", err)
116116
} else if err == nil {
117117
// When the object already exists, there's nothing to do.
118118
return nil
119119
}
120120

121-
if err := createNewAzureSecret(ctx, r.Client, capzManagerBootstrapSecret.Data["azure_client_secret"]); err != nil {
121+
if err := r.createNewAzureSecret(ctx, r.Client, capzManagerBootstrapSecret.Data["azure_client_secret"]); err != nil {
122122
return fmt.Errorf("failed to create Azure Cluster secret: %w", err)
123123
}
124124

125125
return nil
126126
}
127127

128128
// createNewAzureSecret creates a new Azure Cluster Secret.
129-
func createNewAzureSecret(ctx context.Context, cl client.Client, azureClientSecret []byte) error {
129+
func (r *InfraClusterController) createNewAzureSecret(ctx context.Context, cl client.Client, azureClientSecret []byte) error {
130130
azureSecret := &corev1.Secret{
131131
ObjectMeta: metav1.ObjectMeta{
132132
Name: clusterSecretName,
133-
Namespace: defaultCAPINamespace,
133+
Namespace: r.CAPINamespace,
134134
},
135135
Immutable: ptr.To(true),
136136
Data: map[string][]byte{
@@ -149,7 +149,7 @@ func createNewAzureSecret(ctx context.Context, cl client.Client, azureClientSecr
149149
func (r *InfraClusterController) ensureClusterIdentity(ctx context.Context, capzManagerBootstrapSecret corev1.Secret) error {
150150
azureClusterIdentity := &azurev1.AzureClusterIdentity{}
151151
// Get the Azure Cluster Identity.
152-
if err := r.Get(ctx, client.ObjectKey{Namespace: defaultCAPINamespace, Name: r.Infra.Status.InfrastructureName}, azureClusterIdentity); err != nil && !cerrors.IsNotFound(err) {
152+
if err := r.Get(ctx, client.ObjectKey{Namespace: r.CAPINamespace, Name: r.Infra.Status.InfrastructureName}, azureClusterIdentity); err != nil && !cerrors.IsNotFound(err) {
153153
return fmt.Errorf("failed to get Azure Cluster Identity: %w", err)
154154
} else if err == nil {
155155
// When the object already exists, there's nothing to do.
@@ -178,7 +178,7 @@ func (r *InfraClusterController) createAzureClusterIdentity(ctx context.Context,
178178
azureClusterIdentity := &azurev1.AzureClusterIdentity{
179179
ObjectMeta: metav1.ObjectMeta{
180180
Name: r.Infra.Status.InfrastructureName,
181-
Namespace: defaultCAPINamespace,
181+
Namespace: r.CAPINamespace,
182182
Annotations: map[string]string{
183183
// The ManagedBy Annotation is set so CAPI infra providers ignore the InfraCluster object,
184184
// as that's managed externally, in this case by the cluster-capi-operator's infracluster controller.
@@ -228,7 +228,7 @@ func (r *InfraClusterController) ensureAzureInfraCluster(ctx context.Context, ta
228228
return fmt.Errorf("failed to parse apiUrl port: %w", err)
229229
}
230230

231-
providerSpec, err := getAzureMAPIProviderSpec(ctx, r.Client)
231+
providerSpec, err := r.getAzureMAPIProviderSpec(ctx, r.Client)
232232
if err != nil {
233233
return fmt.Errorf("error obtaining Azure Provider Spec: %w", err)
234234
}
@@ -243,7 +243,7 @@ func (r *InfraClusterController) ensureAzureInfraCluster(ctx context.Context, ta
243243
return fmt.Errorf("error creating New Azure Cluster: %w", err)
244244
}
245245

246-
log.Info(fmt.Sprintf("InfraCluster '%s/%s' successfully created", defaultCAPINamespace, r.Infra.Status.InfrastructureName))
246+
log.Info(fmt.Sprintf("InfraCluster '%s/%s' successfully created", r.CAPINamespace, r.Infra.Status.InfrastructureName))
247247

248248
return nil
249249
}
@@ -253,7 +253,7 @@ func (r *InfraClusterController) newAzureCluster(providerSpec *mapiv1beta1.Azure
253253
return &azurev1.AzureCluster{
254254
ObjectMeta: metav1.ObjectMeta{
255255
Name: r.Infra.Status.InfrastructureName,
256-
Namespace: defaultCAPINamespace,
256+
Namespace: r.CAPINamespace,
257257
// The ManagedBy Annotation is set so CAPI infra providers ignore the InfraCluster object,
258258
// as that's managed externally, in this case by this controller.
259259
Annotations: map[string]string{
@@ -267,7 +267,7 @@ func (r *InfraClusterController) newAzureCluster(providerSpec *mapiv1beta1.Azure
267267
AzureEnvironment: "AzurePublicCloud",
268268
IdentityRef: &corev1.ObjectReference{
269269
Name: r.Infra.Status.InfrastructureName,
270-
Namespace: defaultCAPINamespace,
270+
Namespace: r.CAPINamespace,
271271
Kind: "AzureClusterIdentity",
272272
},
273273
},

pkg/controllers/infracluster/gcp.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import (
3737
func (r *InfraClusterController) ensureGCPCluster(ctx context.Context, log logr.Logger) (client.Object, error) {
3838
target := &gcpv1.GCPCluster{ObjectMeta: metav1.ObjectMeta{
3939
Name: r.Infra.Status.InfrastructureName,
40-
Namespace: defaultCAPINamespace,
40+
Namespace: r.CAPINamespace,
4141
}}
4242

4343
// Checking whether InfraCluster object exists. If it doesn't, create it.
@@ -68,15 +68,15 @@ func (r *InfraClusterController) ensureGCPCluster(ctx context.Context, log logr.
6868
return nil, fmt.Errorf("error obtaining GCP Project ID: %w", err)
6969
}
7070

71-
providerSpec, err := getGCPMAPIProviderSpec(ctx, r.Client)
71+
providerSpec, err := r.getGCPMAPIProviderSpec(ctx, r.Client)
7272
if err != nil {
7373
return nil, fmt.Errorf("error obtaining GCP Provider Spec: %w", err)
7474
}
7575

7676
target = &gcpv1.GCPCluster{
7777
ObjectMeta: metav1.ObjectMeta{
7878
Name: r.Infra.Status.InfrastructureName,
79-
Namespace: defaultCAPINamespace,
79+
Namespace: r.CAPINamespace,
8080
// The ManagedBy Annotation is set so CAPI infra providers ignore the InfraCluster object,
8181
// as that's managed externally, in this case by this controller.
8282
Annotations: map[string]string{
@@ -100,14 +100,14 @@ func (r *InfraClusterController) ensureGCPCluster(ctx context.Context, log logr.
100100
return nil, fmt.Errorf("failed to create InfraCluster: %w", err)
101101
}
102102

103-
log.Info(fmt.Sprintf("InfraCluster '%s/%s' successfully created", defaultCAPINamespace, r.Infra.Status.InfrastructureName))
103+
log.Info(fmt.Sprintf("InfraCluster '%s/%s' successfully created", r.CAPINamespace, r.Infra.Status.InfrastructureName))
104104

105105
return target, nil
106106
}
107107

108108
// getGCPMAPIProviderSpec returns a GCP Machine ProviderSpec from the the cluster.
109-
func getGCPMAPIProviderSpec(ctx context.Context, cl client.Client) (*mapiv1beta1.GCPMachineProviderSpec, error) {
110-
rawProviderSpec, err := getRawMAPIProviderSpec(ctx, cl)
109+
func (r *InfraClusterController) getGCPMAPIProviderSpec(ctx context.Context, cl client.Client) (*mapiv1beta1.GCPMachineProviderSpec, error) {
110+
rawProviderSpec, err := r.getRawMAPIProviderSpec(ctx, cl)
111111
if err != nil {
112112
return nil, fmt.Errorf("unable to obtain MAPI ProviderSpec: %w", err)
113113
}
@@ -124,7 +124,7 @@ func getGCPMAPIProviderSpec(ctx context.Context, cl client.Client) (*mapiv1beta1
124124
func (r *InfraClusterController) getGCPProjectID(ctx context.Context) (string, error) {
125125
if r.Infra.Spec.PlatformSpec.GCP == nil || len(r.Infra.Status.PlatformStatus.GCP.ProjectID) == 0 {
126126
// Devise GCP Project ID via MAPI providerSpec.
127-
machineSpec, err := getGCPMAPIProviderSpec(ctx, r.Client)
127+
machineSpec, err := r.getGCPMAPIProviderSpec(ctx, r.Client)
128128
if err != nil || machineSpec == nil {
129129
return "", fmt.Errorf("unable to get GCP MAPI ProviderSpec: %w", err)
130130
}

0 commit comments

Comments
 (0)