• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubevirt / hyperconverged-cluster-operator / 19496636504

19 Nov 2025 09:36AM UTC coverage: 77.322% (+0.08%) from 77.24%
19496636504

Pull #3871

github

web-flow
Merge e04467bdb into 744518506
Pull Request #3871: Allow virt-operator deployment on Hosted Control Planes Cluster

63 of 73 new or added lines in 4 files covered. (86.3%)

12 existing lines in 2 files now uncovered.

7985 of 10327 relevant lines covered (77.32%)

1.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.24
/pkg/internal/nodeinfo/nodeinfo.go
1
package nodeinfo
2

3
import (
4
        "context"
5
        "fmt"
6

7
        "github.com/go-logr/logr"
8
        corev1 "k8s.io/api/core/v1"
9
        "k8s.io/apimachinery/pkg/util/sets"
10
        "sigs.k8s.io/controller-runtime/pkg/client"
11

12
        "github.com/kubevirt/hyperconverged-cluster-operator/api/v1beta1"
13
        "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util"
14
)
15

16
func HandleNodeChanges(ctx context.Context, cl client.Client, hc *v1beta1.HyperConverged, logger logr.Logger) (bool, error) {
1✔
17
        logger.Info("reading cluster nodes")
1✔
18
        nodes, err := getNodes(ctx, cl)
1✔
19
        if err != nil {
1✔
20
                return false, fmt.Errorf("failed to read the cluster nodes; %v", err)
×
UNCOV
21
        }
×
22

23
        return processNodeInfo(nodes, hc), nil
1✔
24
}
25

26
func getNodes(ctx context.Context, cl client.Client) ([]corev1.Node, error) {
2✔
27
        nodesList := &corev1.NodeList{}
2✔
28
        err := cl.List(ctx, nodesList)
2✔
29
        if err != nil {
2✔
30
                return nil, err
×
UNCOV
31
        }
×
32

33
        return nodesList.Items, nil
2✔
34
}
35

36
func processNodeInfo(nodes []corev1.Node, hc *v1beta1.HyperConverged) bool {
1✔
37
        workerNodeCount := 0
1✔
38
        cpNodeCount := 0
1✔
39

1✔
40
        workloadArchs := sets.New[string]()
1✔
41
        cpArchs := sets.New[string]()
1✔
42

1✔
43
        isWorkloadNode := isWorkloadNodeFunc(hc)
1✔
44

1✔
45
        for _, node := range nodes {
2✔
46
                arch := node.Status.NodeInfo.Architecture
1✔
47
                if isWorkerNode(node) {
2✔
48
                        workerNodeCount++
1✔
49
                }
1✔
50

51
                if isWorkloadNode(node) {
2✔
52
                        workloadArchs.Insert(arch)
1✔
53
                }
1✔
54

55
                _, masterLabelExists := node.Labels[LabelNodeRoleMaster]
1✔
56
                _, cpLabelExists := node.Labels[LabelNodeRoleControlPlane]
1✔
57
                if masterLabelExists || cpLabelExists {
2✔
58
                        cpNodeCount++
1✔
59
                        cpArchs.Insert(arch)
1✔
60
                }
1✔
61
        }
62

63
        // remove empty architectures
64
        workloadArchs.Delete("")
1✔
65
        cpArchs.Delete("")
1✔
66

1✔
67
        newValue := cpNodeCount >= 3
1✔
68
        changed := controlPlaneHighlyAvailable.Swap(newValue) != newValue
1✔
69

1✔
70
        newValue = cpNodeCount >= 1
1✔
71
        changed = controlPlaneNodeExist.Swap(newValue) != newValue || changed
1✔
72

1✔
73
        newValue = workerNodeCount >= 2
1✔
74
        changed = infrastructureHighlyAvailable.Swap(newValue) != newValue || changed
1✔
75

1✔
76
        changed = workloadArchitectures.set(workloadArchs) || changed
1✔
77
        changed = controlPlaneArchitectures.set(cpArchs) || changed
1✔
78

1✔
79
        return changed
1✔
80
}
81

82
func isWorkerNode(node corev1.Node) bool {
1✔
83
        _, exists := node.Labels[LabelNodeRoleWorker]
1✔
84
        return exists
1✔
85
}
1✔
86

87
func isWorkloadNodeFunc(hc *v1beta1.HyperConverged) func(corev1.Node) bool {
1✔
88
        if hasWorkloadRequirements(hc) {
2✔
89

1✔
90
                workloadMatcher := getWorkloadMatcher(hc)
1✔
91

1✔
92
                return func(node corev1.Node) bool {
2✔
93
                        matches, err := workloadMatcher.Match(&node)
1✔
94
                        if err != nil { // should not happen, because the validation webhook checks it, but just in case
1✔
95
                                return false
×
UNCOV
96
                        }
×
97
                        return matches
1✔
98
                }
99
        }
100

101
        return isWorkerNode
1✔
102
}
103

104
// HandleHyperShiftNodeLabeling manages the control-plane label on worker nodes for HyperShift managed clusters
105
func HandleHyperShiftNodeLabeling(ctx context.Context, cl client.Client, logger logr.Logger) error {
2✔
106
        const hypershiftLabelValue = "set-to-allow-kubevirt-deployment"
2✔
107

2✔
108
        clusterInfo := util.GetClusterInfo()
2✔
109

2✔
110
        // Check if we're running on OpenShift and if it's HyperShift managed
2✔
111
        shouldLabelNodes := clusterInfo.IsOpenshift() && clusterInfo.IsHyperShiftManaged()
2✔
112

2✔
113
        logger.Info("HyperShift node labeling check",
2✔
114
                "isOpenshift", clusterInfo.IsOpenshift(),
2✔
115
                "isHyperShiftManaged", clusterInfo.IsHyperShiftManaged(),
2✔
116
                "shouldLabelNodes", shouldLabelNodes,
2✔
117
        )
2✔
118

2✔
119
        // Get all nodes
2✔
120
        nodes, err := getNodes(ctx, cl)
2✔
121
        if err != nil {
2✔
NEW
UNCOV
122
                return fmt.Errorf("failed to list nodes for HyperShift labeling: %w", err)
×
NEW
UNCOV
123
        }
×
124

125
        for _, node := range nodes {
3✔
126
                needsUpdate := false
1✔
127
                updatedNode := node.DeepCopy()
1✔
128

1✔
129
                if shouldLabelNodes {
2✔
130
                        // Add label to worker nodes that don't have it
1✔
131
                        if isWorkerNode(node) {
2✔
132
                                currentValue, hasLabel := updatedNode.Labels[LabelNodeRoleControlPlane]
1✔
133
                                if !hasLabel || currentValue != hypershiftLabelValue {
2✔
134
                                        if updatedNode.Labels == nil {
1✔
NEW
UNCOV
135
                                                updatedNode.Labels = make(map[string]string)
×
NEW
UNCOV
136
                                        }
×
137
                                        updatedNode.Labels[LabelNodeRoleControlPlane] = hypershiftLabelValue
1✔
138
                                        needsUpdate = true
1✔
139
                                        logger.Info("Adding control-plane label to worker node",
1✔
140
                                                "node", node.Name,
1✔
141
                                                "labelValue", hypershiftLabelValue,
1✔
142
                                        )
1✔
143
                                }
144
                        }
145
                } else {
1✔
146
                        // Remove the label if it exists with our specific value
1✔
147
                        if currentValue, hasLabel := updatedNode.Labels[LabelNodeRoleControlPlane]; hasLabel && currentValue == hypershiftLabelValue {
2✔
148
                                delete(updatedNode.Labels, LabelNodeRoleControlPlane)
1✔
149
                                needsUpdate = true
1✔
150
                                logger.Info("Removing HyperShift control-plane label from node",
1✔
151
                                        "node", node.Name,
1✔
152
                                )
1✔
153
                        }
1✔
154
                }
155

156
                if needsUpdate {
2✔
157
                        if err := cl.Update(ctx, updatedNode); err != nil {
1✔
NEW
UNCOV
158
                                return fmt.Errorf("failed to update node %s: %w", node.Name, err)
×
NEW
UNCOV
159
                        }
×
160
                        logger.Info("Successfully updated node labels",
1✔
161
                                "node", node.Name,
1✔
162
                        )
1✔
163
                }
164
        }
165

166
        return nil
2✔
167
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc