Skip to content

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import (

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"

capiv1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
Expand Down Expand Up @@ -107,10 +109,24 @@ func TestMergePullSecrets(t *testing.T) {
wantErr: false,
},
{
name: "overwrite existing registry",
name: "conflict resolution - original always wins",
originalSecret: composePullSecretBytes(map[string]string{"registry1": oldAuth}),
additionalSecret: composePullSecretBytes(map[string]string{"registry1": validAuth}),
expectedResult: composePullSecretBytes(map[string]string{"registry1": validAuth}),
expectedResult: composePullSecretBytes(map[string]string{"registry1": oldAuth}),
wantErr: false,
},
{
name: "precedence test - original always has precedence",
originalSecret: composePullSecretBytes(map[string]string{"registry1": oldAuth, "registry2": oldAuth}),
additionalSecret: composePullSecretBytes(map[string]string{"registry1": validAuth, "registry3": validAuth}),
expectedResult: composePullSecretBytes(map[string]string{"registry1": oldAuth, "registry2": oldAuth, "registry3": validAuth}),
wantErr: false,
},
{
name: "multiple conflicts - original always wins",
originalSecret: composePullSecretBytes(map[string]string{"registry1": oldAuth, "registry2": oldAuth}),
additionalSecret: composePullSecretBytes(map[string]string{"registry1": validAuth, "registry2": validAuth, "registry3": validAuth}),
expectedResult: composePullSecretBytes(map[string]string{"registry1": oldAuth, "registry2": oldAuth, "registry3": validAuth}),
wantErr: false,
},
{
Expand Down Expand Up @@ -279,3 +295,304 @@ func TestAdditionalPullSecretExists(t *testing.T) {
})
}
}

func TestLabelNodesForGlobalPullSecret(t *testing.T) {
tests := []struct {
name string
nodes []corev1.Node
machineSets []capiv1.MachineSet
machines []capiv1.Machine
expectedLabeled []string // names of nodes that should have the label
}{
{
name: "Replace-InPlace-Replace scenario: only Replace nodes should be labeled",
nodes: []corev1.Node{
{
ObjectMeta: metav1.ObjectMeta{
Name: "replace-node-1",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "inplace-node-1",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "replace-node-2",
},
},
},
machineSets: []capiv1.MachineSet{
// First NodePool: Replace strategy (no InPlace annotations)
{
ObjectMeta: metav1.ObjectMeta{
Name: "replace-machineset-1",
Namespace: "test-namespace",
},
Spec: capiv1.MachineSetSpec{
Selector: metav1.LabelSelector{
MatchLabels: map[string]string{
"machineset": "replace-1",
},
},
},
},
// Second NodePool: InPlace strategy (has InPlace annotations)
{
ObjectMeta: metav1.ObjectMeta{
Name: "inplace-machineset-1",
Namespace: "test-namespace",
Annotations: map[string]string{
"hypershift.openshift.io/nodePoolTargetConfigVersion": "config-hash-123",
"hypershift.openshift.io/nodePoolCurrentConfigVersion": "config-hash-456",
},
},
Spec: capiv1.MachineSetSpec{
Selector: metav1.LabelSelector{
MatchLabels: map[string]string{
"machineset": "inplace-1",
},
},
},
},
// Third NodePool: Replace strategy (no InPlace annotations) - this should work after InPlace
{
ObjectMeta: metav1.ObjectMeta{
Name: "replace-machineset-2",
Namespace: "test-namespace",
},
Spec: capiv1.MachineSetSpec{
Selector: metav1.LabelSelector{
MatchLabels: map[string]string{
"machineset": "replace-2",
},
},
},
},
},
machines: []capiv1.Machine{
// Machine for first Replace NodePool
{
ObjectMeta: metav1.ObjectMeta{
Name: "replace-machine-1",
Namespace: "test-namespace",
Labels: map[string]string{
"machineset": "replace-1",
},
},
Status: capiv1.MachineStatus{
NodeRef: &corev1.ObjectReference{
Name: "replace-node-1",
},
},
},
// Machine for InPlace NodePool
{
ObjectMeta: metav1.ObjectMeta{
Name: "inplace-machine-1",
Namespace: "test-namespace",
Labels: map[string]string{
"machineset": "inplace-1",
},
},
Status: capiv1.MachineStatus{
NodeRef: &corev1.ObjectReference{
Name: "inplace-node-1",
},
},
},
// Machine for second Replace NodePool (created after InPlace)
{
ObjectMeta: metav1.ObjectMeta{
Name: "replace-machine-2",
Namespace: "test-namespace",
Labels: map[string]string{
"machineset": "replace-2",
},
},
Status: capiv1.MachineStatus{
NodeRef: &corev1.ObjectReference{
Name: "replace-node-2",
},
},
},
},
expectedLabeled: []string{"replace-node-1", "replace-node-2"}, // Both Replace nodes should be labeled
},
{
name: "Only InPlace NodePools: no nodes should be labeled",
nodes: []corev1.Node{
{
ObjectMeta: metav1.ObjectMeta{
Name: "inplace-node-1",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "inplace-node-2",
},
},
},
machineSets: []capiv1.MachineSet{
{
ObjectMeta: metav1.ObjectMeta{
Name: "inplace-machineset-1",
Namespace: "test-namespace",
Annotations: map[string]string{
"hypershift.openshift.io/nodePoolTargetConfigVersion": "config-hash-123",
},
},
Spec: capiv1.MachineSetSpec{
Selector: metav1.LabelSelector{
MatchLabels: map[string]string{
"machineset": "inplace-1",
},
},
},
},
},
machines: []capiv1.Machine{
{
ObjectMeta: metav1.ObjectMeta{
Name: "inplace-machine-1",
Namespace: "test-namespace",
Labels: map[string]string{
"machineset": "inplace-1",
},
},
Status: capiv1.MachineStatus{
NodeRef: &corev1.ObjectReference{
Name: "inplace-node-1",
},
},
},
},
expectedLabeled: []string{}, // No nodes should be labeled
},
{
name: "Only Replace NodePools: all nodes should be labeled",
nodes: []corev1.Node{
{
ObjectMeta: metav1.ObjectMeta{
Name: "replace-node-1",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "replace-node-2",
},
},
},
machineSets: []capiv1.MachineSet{
{
ObjectMeta: metav1.ObjectMeta{
Name: "replace-machineset-1",
Namespace: "test-namespace",
// No InPlace annotations
},
Spec: capiv1.MachineSetSpec{
Selector: metav1.LabelSelector{
MatchLabels: map[string]string{
"machineset": "replace-1",
},
},
},
},
},
machines: []capiv1.Machine{
{
ObjectMeta: metav1.ObjectMeta{
Name: "replace-machine-1",
Namespace: "test-namespace",
Labels: map[string]string{
"machineset": "replace-1",
},
},
Status: capiv1.MachineStatus{
NodeRef: &corev1.ObjectReference{
Name: "replace-node-1",
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "replace-machine-2",
Namespace: "test-namespace",
Labels: map[string]string{
"machineset": "replace-1",
},
},
Status: capiv1.MachineStatus{
NodeRef: &corev1.ObjectReference{
Name: "replace-node-2",
},
},
},
},
expectedLabeled: []string{"replace-node-1", "replace-node-2"}, // All Replace nodes should be labeled
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)

// Create runtime scheme and add required types
scheme := runtime.NewScheme()
_ = corev1.AddToScheme(scheme)
_ = capiv1.AddToScheme(scheme)

// Convert to client.Object slices
var objects []client.Object
for i := range tt.nodes {
objects = append(objects, &tt.nodes[i])
}
for i := range tt.machineSets {
objects = append(objects, &tt.machineSets[i])
}
for i := range tt.machines {
objects = append(objects, &tt.machines[i])
}

// Create fake clients
cpClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
kubeSystemSecretClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
nodeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
hcUncachedClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()

// Create reconciler
reconciler := &Reconciler{
cpClient: cpClient,
kubeSystemSecretClient: kubeSystemSecretClient,
nodeClient: nodeClient,
hcUncachedClient: hcUncachedClient,
hcpNamespace: "test-namespace",
}

// Execute the function under test
err := reconciler.labelNodesForGlobalPullSecret(context.Background())
g.Expect(err).NotTo(HaveOccurred())

// Check that only expected nodes have the label
nodeList := &corev1.NodeList{}
err = nodeClient.List(context.Background(), nodeList)
g.Expect(err).NotTo(HaveOccurred())

labeledNodes := make(map[string]bool)
for _, node := range nodeList.Items {
if node.Labels != nil && node.Labels[globalPSLabelKey] == "true" {
labeledNodes[node.Name] = true
}
}

// Verify expected nodes are labeled
for _, expectedNode := range tt.expectedLabeled {
g.Expect(labeledNodes[expectedNode]).To(BeTrue(), "Node %s should be labeled but wasn't", expectedNode)
}

// Verify no unexpected nodes are labeled
g.Expect(len(labeledNodes)).To(Equal(len(tt.expectedLabeled)), "Number of labeled nodes doesn't match expected")
})
}
}
Loading