Skip to content

Commit bd13c66

Browse files
committed
Efficient, threadsafe data initialization
1 parent fc06a6c commit bd13c66

File tree

4 files changed

+135
-110
lines changed

4 files changed

+135
-110
lines changed

pkg/webhook/preflight/nutanix/checker.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,23 @@ import (
77
"context"
88
"sync"
99

10-
prismv4 "github.com/nutanix-cloud-native/prism-go-client/v4"
1110
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
1211
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
1312

13+
prismv4 "github.com/nutanix-cloud-native/prism-go-client/v4"
14+
15+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/variables"
1416
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/webhook/preflight"
1517
)
1618

1719
type Checker struct {
18-
client ctrlclient.Client
20+
client ctrlclient.Client
21+
cluster *clusterv1.Cluster
22+
1923
nutanixClient *prismv4.Client
20-
cluster *clusterv1.Cluster
2124

22-
clientMutex sync.Mutex
25+
workerConfigGetterByMachineDeploymentName map[string]func() (*variables.WorkerNodeConfigSpec, error)
26+
workerConfigGetterByMachineDeploymentNameMutex sync.Mutex
2327
}
2428

2529
func (n *Checker) Init(

pkg/webhook/preflight/nutanix/client.go

Lines changed: 49 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3,72 +3,66 @@ package nutanix
33
import (
44
"context"
55
"fmt"
6+
"sync"
7+
8+
corev1 "k8s.io/api/core/v1"
9+
"k8s.io/apimachinery/pkg/types"
610

711
prism "github.com/nutanix-cloud-native/prism-go-client"
812
prismcredentials "github.com/nutanix-cloud-native/prism-go-client/environment/credentials"
913
prismv4 "github.com/nutanix-cloud-native/prism-go-client/v4"
10-
corev1 "k8s.io/api/core/v1"
11-
"k8s.io/apimachinery/pkg/types"
12-
ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
1314

14-
carenv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
1515
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/variables"
1616
)
1717

18-
func (n *Checker) v4client(
19-
ctx context.Context,
20-
client ctrlclient.Client,
21-
clusterNamespace string,
22-
) (
23-
*prismv4.Client,
24-
error,
25-
) {
26-
n.clientMutex.Lock()
27-
defer n.clientMutex.Unlock()
28-
if n.nutanixClient != nil {
29-
return n.nutanixClient, nil
30-
}
31-
32-
clusterConfig, err := variables.UnmarshalClusterConfigVariable(n.cluster.Spec.Topology.Variables)
33-
if err != nil {
34-
return nil, fmt.Errorf("failed to unmarshal cluster variable %q: %w", carenv1.ClusterConfigVariableName, err)
35-
}
18+
// clientV4 creates a new Prism V4 client for the Nutanix cluster.
19+
// It retrieves the Prism Central credentials from the provided client and
20+
// uses them to create the client. The client is cached for future use.
21+
// The function returns an error if the credentials cannot be retrieved or
22+
// if the Prism Central endpoint cannot be parsed.
23+
func (n *Checker) clientV4(ctx context.Context, clusterConfig *variables.ClusterConfigSpec) (*prismv4.Client, error) {
24+
return sync.OnceValues(func() (*prismv4.Client, error) {
25+
if clusterConfig == nil || clusterConfig.Nutanix == nil {
26+
return nil, fmt.Errorf("clusterConfig variable is nil or does not contain Nutanix config")
27+
}
3628

37-
if clusterConfig.Nutanix == nil {
38-
return nil, fmt.Errorf("missing Nutanix configuration in cluster topology")
39-
}
29+
if clusterConfig.Nutanix.PrismCentralEndpoint.Credentials.SecretRef.Name == "" {
30+
return nil, fmt.Errorf("prism Central credentials reference SecretRef.Name has no value")
31+
}
4032

41-
if clusterConfig.Nutanix.PrismCentralEndpoint.Credentials.SecretRef.Name == "" {
42-
return nil, fmt.Errorf("prism Central credentials reference SecretRef.Name has no value")
43-
}
33+
credentialsSecret := &corev1.Secret{}
34+
if err := n.client.Get(
35+
ctx,
36+
types.NamespacedName{
37+
Namespace: n.cluster.Namespace,
38+
Name: clusterConfig.Nutanix.PrismCentralEndpoint.Credentials.SecretRef.Name,
39+
},
40+
credentialsSecret,
41+
); err != nil {
42+
return nil, fmt.Errorf("failed to get Prism Central credentials Secret: %w", err)
43+
}
4444

45-
credentialsSecret := &corev1.Secret{}
46-
if err := client.Get(
47-
ctx,
48-
types.NamespacedName{
49-
Namespace: clusterNamespace,
50-
Name: clusterConfig.Nutanix.PrismCentralEndpoint.Credentials.SecretRef.Name,
51-
},
52-
credentialsSecret,
53-
); err != nil {
54-
return nil, fmt.Errorf("failed to get Prism Central credentials Secret: %w", err)
55-
}
45+
// Get username and password
46+
credentials, err := prismcredentials.ParseCredentials(credentialsSecret.Data["credentials"])
47+
if err != nil {
48+
return nil, fmt.Errorf("failed to parse Prism Central credentials from Secret: %w", err)
49+
}
5650

57-
// Get username and password
58-
credentials, err := prismcredentials.ParseCredentials(credentialsSecret.Data["credentials"])
59-
if err != nil {
60-
return nil, fmt.Errorf("failed to parse Prism Central credentials from Secret: %w", err)
61-
}
51+
host, port, err := clusterConfig.Nutanix.PrismCentralEndpoint.ParseURL()
52+
if err != nil {
53+
return nil, fmt.Errorf("failed to parse Prism Central endpoint: %w", err)
54+
}
6255

63-
host, port, err := clusterConfig.Nutanix.PrismCentralEndpoint.ParseURL()
64-
if err != nil {
65-
return nil, fmt.Errorf("failed to parse Prism Central endpoint: %w", err)
66-
}
67-
68-
return prismv4.NewV4Client(prism.Credentials{
69-
Endpoint: fmt.Sprintf("%s:%d", host, port),
70-
Username: credentials.Username,
71-
Password: credentials.Password,
72-
Insecure: clusterConfig.Nutanix.PrismCentralEndpoint.Insecure,
73-
})
56+
nutanixClient, err := prismv4.NewV4Client(prism.Credentials{
57+
Endpoint: fmt.Sprintf("%s:%d", host, port),
58+
Username: credentials.Username,
59+
Password: credentials.Password,
60+
Insecure: clusterConfig.Nutanix.PrismCentralEndpoint.Insecure,
61+
})
62+
if err != nil {
63+
return nil, fmt.Errorf("failed to create Prism V4 client: %w", err)
64+
}
65+
n.nutanixClient = nutanixClient
66+
return n.nutanixClient, nil
67+
})()
7468
}

pkg/webhook/preflight/nutanix/image.go

Lines changed: 29 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import (
44
"context"
55
"fmt"
66

7-
prismv4 "github.com/nutanix-cloud-native/prism-go-client/v4"
87
vmmv4 "github.com/nutanix/ntnx-api-golang-clients/vmm-go-client/v4/models/vmm/v4/content"
98
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
109

10+
prismv4 "github.com/nutanix-cloud-native/prism-go-client/v4"
11+
1112
capxv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1"
1213
carenv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
1314
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/variables"
@@ -20,79 +21,55 @@ func (n *Checker) VMImageCheck(ctx context.Context) preflight.CheckResult {
2021
}
2122

2223
// Check control plane VM image.
23-
clusterConfig, err := variables.UnmarshalClusterConfigVariable(n.cluster.Spec.Topology.Variables)
24+
clusterConfig, err := n.getClusterConfig()
2425
if err != nil {
2526
result.Error = true
2627
result.Causes = append(result.Causes, metav1.StatusCause{
27-
Type: "VMImageCheck",
28-
Message: fmt.Sprintf(
29-
"failed to unmarshal topology variable %q: %s",
30-
carenv1.ClusterConfigVariableName,
31-
err,
32-
),
33-
Field: "cluster.spec.topology.variables",
28+
Type: "VMImageCheck",
29+
Message: fmt.Sprintf("failed to read clusterConfig variable: %s", err),
30+
Field: "cluster.spec.topology.variables",
3431
})
35-
} else if clusterConfig != nil {
36-
if clusterConfig.ControlPlane == nil || clusterConfig.ControlPlane.Nutanix == nil {
37-
result.Causes = append(result.Causes, metav1.StatusCause{
38-
Type: "VMImageCheck",
39-
Message: "missing Nutanix configuration in cluster topology",
40-
Field: "cluster.spec.topology.controlPlane.nutanix",
41-
})
42-
}
43-
32+
}
33+
if clusterConfig != nil && clusterConfig.ControlPlane != nil && clusterConfig.ControlPlane.Nutanix != nil {
4434
n.vmImageCheckForMachineDetails(
4535
ctx,
36+
clusterConfig,
4637
&clusterConfig.ControlPlane.Nutanix.MachineDetails,
47-
"controlPlane.nutanix.machineDetails",
38+
"cluster.spec.topology.variables[.name=clusterConfig].controlPlane.nutanix.machineDetails",
4839
&result,
4940
)
5041
}
5142

52-
// Check worker VM images.
43+
// If there is no worker topology, return early.
5344
if n.cluster.Spec.Topology.Workers == nil {
5445
return result
5546
}
5647

48+
// Check worker VM images.
5749
for _, md := range n.cluster.Spec.Topology.Workers.MachineDeployments {
58-
if md.Variables == nil {
59-
continue
60-
}
61-
62-
workerConfig, err := variables.UnmarshalWorkerConfigVariable(md.Variables.Overrides)
50+
workerConfig, err := n.getWorkerConfigForMachineDeployment(md)
6351
if err != nil {
6452
result.Error = true
6553
result.Causes = append(result.Causes, metav1.StatusCause{
66-
Type: "VMImageCheck",
67-
Message: fmt.Sprintf(
68-
"failed to unmarshal topology variable %q: %s",
69-
carenv1.WorkerConfigVariableName,
70-
err,
71-
),
54+
Type: "VMImageCheck",
55+
Message: fmt.Sprintf("failed to read workerConfig variable: %s", err),
7256
Field: fmt.Sprintf(
7357
"cluster.spec.topology.workers.machineDeployments[.name=%s].variables.overrides",
7458
md.Name,
7559
),
7660
})
77-
} else if workerConfig != nil {
78-
if workerConfig.Nutanix == nil {
79-
result.Causes = append(result.Causes, metav1.StatusCause{
80-
Type: "VMImageCheck",
81-
Message: "missing Nutanix configuration in worker machine deployment",
82-
Field: fmt.Sprintf("cluster.spec.topology.workers.machineDeployments[.name=%s]"+
83-
".variables.overrides[.name=workerConfig].value.nutanix", md.Name),
84-
})
85-
} else {
86-
n.vmImageCheckForMachineDetails(
87-
ctx,
88-
&workerConfig.Nutanix.MachineDetails,
89-
fmt.Sprintf(
90-
"workers.machineDeployments[.name=%s].variables.overrides[.name=workerConfig].value.nutanix.machineDetails",
91-
md.Name,
92-
),
93-
&result,
94-
)
95-
}
61+
}
62+
if workerConfig != nil && workerConfig.Nutanix != nil {
63+
n.vmImageCheckForMachineDetails(
64+
ctx,
65+
clusterConfig,
66+
&workerConfig.Nutanix.MachineDetails,
67+
fmt.Sprintf(
68+
"workers.machineDeployments[.name=%s].variables.overrides[.name=workerConfig].value.nutanix.machineDetails",
69+
md.Name,
70+
),
71+
&result,
72+
)
9673
}
9774
}
9875

@@ -101,6 +78,7 @@ func (n *Checker) VMImageCheck(ctx context.Context) preflight.CheckResult {
10178

10279
func (n *Checker) vmImageCheckForMachineDetails(
10380
ctx context.Context,
81+
clusterConfig *variables.ClusterConfigSpec,
10482
details *carenv1.NutanixMachineDetails,
10583
field string,
10684
result *preflight.CheckResult,
@@ -117,7 +95,7 @@ func (n *Checker) vmImageCheckForMachineDetails(
11795
}
11896

11997
if details.Image != nil {
120-
client, err := n.v4client(ctx, n.client, n.cluster.Namespace)
98+
client, err := n.clientV4(ctx, clusterConfig)
12199
if err != nil {
122100
result.Allowed = false
123101
result.Error = true
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package nutanix
2+
3+
import (
4+
"fmt"
5+
"sync"
6+
7+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
8+
9+
"github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/variables"
10+
)
11+
12+
func (n *Checker) getClusterConfig() (*variables.ClusterConfigSpec, error) {
13+
return sync.OnceValues(func() (*variables.ClusterConfigSpec, error) {
14+
if n.cluster.Spec.Topology.Variables == nil {
15+
return nil, nil
16+
}
17+
18+
clusterConfig, err := variables.UnmarshalClusterConfigVariable(n.cluster.Spec.Topology.Variables)
19+
if err != nil {
20+
return nil, fmt.Errorf("failed to unmarshal .variables[.name=clusterConfig]: %w", err)
21+
}
22+
23+
return clusterConfig, nil
24+
})()
25+
}
26+
27+
func (n *Checker) getWorkerConfigForMachineDeployment(
28+
md clusterv1.MachineDeploymentTopology,
29+
) (*variables.WorkerNodeConfigSpec, error) {
30+
n.workerConfigGetterByMachineDeploymentNameMutex.Lock()
31+
defer n.workerConfigGetterByMachineDeploymentNameMutex.Unlock()
32+
33+
fn, ok := n.workerConfigGetterByMachineDeploymentName[md.Name]
34+
if !ok {
35+
fn = sync.OnceValues(func() (*variables.WorkerNodeConfigSpec, error) {
36+
if md.Variables == nil {
37+
return nil, nil
38+
}
39+
40+
workerConfig, err := variables.UnmarshalWorkerConfigVariable(md.Variables.Overrides)
41+
if err != nil {
42+
return nil, fmt.Errorf("failed to unmarshal .variables.overrides[.name=workerConfig]: %w", err)
43+
}
44+
45+
return workerConfig, nil
46+
})
47+
}
48+
return fn()
49+
}

0 commit comments

Comments
 (0)