Skip to content

Commit ab01534

Browse files
committed
HyperNode supports select Nodes By labels
Signed-off-by: wangbin <[email protected]>
1 parent 1d69621 commit ab01534

File tree

5 files changed

+290
-7
lines changed

5 files changed

+290
-7
lines changed

docs/design/Network Topology Aware Scheduling.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -458,16 +458,20 @@ type MemberSpec struct {
458458
// regexMatch:
459459
// pattern: "^node-[0-9]+$"
460460
//
461-
// +kubebuilder:validation:XValidation:rule="self.exactMatch != null || self.regexMatch != null",message="Either ExactMatch or RegexMatch must be specified"
462-
// +kubebuilder:validation:XValidation:rule="!(self.exactMatch != null && self.regexMatch != null)",message="ExactMatch and RegexMatch cannot be specified together"
461+
// +kubebuilder:validation:XValidation:rule="has(self.exactMatch) || has(self.regexMatch) || has(self.LabelMatch)",message="Either ExactMatch or RegexMatch or LabelMatch must be specified"
462+
// +kubebuilder:validation:XValidation:rule="!(has(self.exactMatch) && has(self.regexMatch) && has(self.LabelMatch))",message="ExactMatch and RegexMatch and LabelMatch cannot be specified together"
463463
type MemberSelector struct {
464-
// ExactMatch defines the exact match criteria (required when Type is "Exact").
464+
// ExactMatch defines the exact match criteria.
465465
// +optional
466466
ExactMatch *ExactMatch `json:"exactMatch,omitempty"`
467467

468-
// RegexMatch defines the regex match criteria (required when Type is "Regex").
468+
// RegexMatch defines the regex match criteria.
469469
// +optional
470470
RegexMatch *RegexMatch `json:"regexMatch,omitempty"`
471+
472+
// LabelMatch defines the labels match criteria (only take effect when Member Type is "Node").
473+
// +optional
474+
LabelMatch *metav1.LabelSelector `json:"labelMatch,omitempty"`
471475
}
472476

473477
// ExactMatch represents the criteria for exact name matching.

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ require (
4646
sigs.k8s.io/controller-runtime v0.13.0
4747
sigs.k8s.io/yaml v1.4.0
4848
stathat.com/c/consistent v1.0.0
49-
volcano.sh/apis v1.11.0-network-topology-preview.0
49+
volcano.sh/apis v0.0.0-20250306023628-7264f8fe811c
5050
)
5151

5252
require (

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -510,5 +510,5 @@ sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
510510
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
511511
stathat.com/c/consistent v1.0.0 h1:ezyc51EGcRPJUxfHGSgJjWzJdj3NiMU9pNfLNGiXV0c=
512512
stathat.com/c/consistent v1.0.0/go.mod h1:QkzMWzcbB+yQBL2AttO6sgsQS/JSTapcDISJalmCDS0=
513-
volcano.sh/apis v1.11.0-network-topology-preview.0 h1:lUdLoNxgXks/yD5Q5xAkKF4dojSPuuyg2YwxpiXRIVU=
514-
volcano.sh/apis v1.11.0-network-topology-preview.0/go.mod h1:FOdmG++9+8lgENJ9XXDh+O3Jcb9YVRnlMSpgIh3NSVI=
513+
volcano.sh/apis v0.0.0-20250306023628-7264f8fe811c h1:7asOFV/P5TV8kJaf7A/nHx7K2ZGrhQPdgye1V8aIWsk=
514+
volcano.sh/apis v0.0.0-20250306023628-7264f8fe811c/go.mod h1:FOdmG++9+8lgENJ9XXDh+O3Jcb9YVRnlMSpgIh3NSVI=

pkg/scheduler/api/hyper_node_info.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,19 @@ func (hni *HyperNodesInfo) getMembers(selector topologyv1alpha1.MemberSelector,
447447
}
448448
}
449449
}
450+
if selector.LabelMatch != nil {
451+
labelSelector, err := metav1.LabelSelectorAsSelector(selector.LabelMatch)
452+
if err != nil {
453+
klog.ErrorS(err, "Failed to construct labelSelector as labelMatch", "LabelMatch", selector.LabelMatch)
454+
return sets.Set[string]{}
455+
}
456+
for _, node := range nodes {
457+
nodeLabels := labels.Set(node.Labels)
458+
if labelSelector.Matches(nodeLabels) {
459+
members.Insert(node.Name)
460+
}
461+
}
462+
}
450463
return members
451464
}
452465

pkg/scheduler/api/hyper_node_info_test.go

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"testing"
2222

2323
"github.com/stretchr/testify/assert"
24+
corev1 "k8s.io/api/core/v1"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2426
"k8s.io/apimachinery/pkg/util/sets"
2527
"k8s.io/client-go/informers"
2628
fakeclientset "k8s.io/client-go/kubernetes/fake"
@@ -588,3 +590,267 @@ func TestGetLCAHyperNode(t *testing.T) {
588590
})
589591
}
590592
}
593+
594+
func TestGetMembers(t *testing.T) {
595+
type testCase struct {
596+
name string
597+
selector topologyv1alpha1.MemberSelector
598+
nodes []*corev1.Node
599+
expected sets.Set[string]
600+
}
601+
602+
testCases := []testCase{
603+
{
604+
name: "Label match success with MatchLabels",
605+
selector: topologyv1alpha1.MemberSelector{
606+
LabelMatch: &metav1.LabelSelector{
607+
MatchLabels: map[string]string{
608+
"role": "worker",
609+
},
610+
},
611+
},
612+
nodes: []*corev1.Node{
613+
{
614+
ObjectMeta: metav1.ObjectMeta{
615+
Name: "node1",
616+
Labels: map[string]string{
617+
"role": "worker",
618+
},
619+
},
620+
},
621+
{
622+
ObjectMeta: metav1.ObjectMeta{
623+
Name: "node2",
624+
Labels: map[string]string{
625+
"role": "master",
626+
},
627+
},
628+
},
629+
},
630+
expected: sets.New[string]("node1"),
631+
},
632+
{
633+
name: "Label match failure with MatchLabels",
634+
selector: topologyv1alpha1.MemberSelector{
635+
LabelMatch: &metav1.LabelSelector{
636+
MatchLabels: map[string]string{
637+
"role": "invalid-role",
638+
},
639+
},
640+
},
641+
nodes: []*corev1.Node{
642+
{
643+
ObjectMeta: metav1.ObjectMeta{
644+
Name: "node1",
645+
Labels: map[string]string{
646+
"role": "worker",
647+
},
648+
},
649+
},
650+
{
651+
ObjectMeta: metav1.ObjectMeta{
652+
Name: "node2",
653+
Labels: map[string]string{
654+
"role": "master",
655+
},
656+
},
657+
},
658+
},
659+
expected: sets.New[string](),
660+
},
661+
{
662+
name: "Label selector is nil",
663+
selector: topologyv1alpha1.MemberSelector{
664+
LabelMatch: nil,
665+
},
666+
nodes: []*corev1.Node{
667+
{
668+
ObjectMeta: metav1.ObjectMeta{
669+
Name: "node1",
670+
Labels: map[string]string{
671+
"role": "worker",
672+
},
673+
},
674+
},
675+
{
676+
ObjectMeta: metav1.ObjectMeta{
677+
Name: "node2",
678+
Labels: map[string]string{
679+
"role": "master",
680+
},
681+
},
682+
},
683+
},
684+
expected: sets.New[string](),
685+
},
686+
{
687+
name: "MatchExpressions In operator match success",
688+
selector: topologyv1alpha1.MemberSelector{
689+
LabelMatch: &metav1.LabelSelector{
690+
MatchExpressions: []metav1.LabelSelectorRequirement{
691+
{
692+
Key: "role",
693+
Operator: metav1.LabelSelectorOpIn,
694+
Values: []string{"worker"},
695+
},
696+
},
697+
},
698+
},
699+
nodes: []*corev1.Node{
700+
{
701+
ObjectMeta: metav1.ObjectMeta{
702+
Name: "node1",
703+
Labels: map[string]string{
704+
"role": "worker",
705+
},
706+
},
707+
},
708+
{
709+
ObjectMeta: metav1.ObjectMeta{
710+
Name: "node2",
711+
Labels: map[string]string{
712+
"role": "master",
713+
},
714+
},
715+
},
716+
},
717+
expected: sets.New[string]("node1"),
718+
},
719+
{
720+
name: "MatchExpressions In operator match failure",
721+
selector: topologyv1alpha1.MemberSelector{
722+
LabelMatch: &metav1.LabelSelector{
723+
MatchExpressions: []metav1.LabelSelectorRequirement{
724+
{
725+
Key: "role",
726+
Operator: metav1.LabelSelectorOpIn,
727+
Values: []string{"invalid-role"},
728+
},
729+
},
730+
},
731+
},
732+
nodes: []*corev1.Node{
733+
{
734+
ObjectMeta: metav1.ObjectMeta{
735+
Name: "node1",
736+
Labels: map[string]string{
737+
"role": "worker",
738+
},
739+
},
740+
},
741+
{
742+
ObjectMeta: metav1.ObjectMeta{
743+
Name: "node2",
744+
Labels: map[string]string{
745+
"role": "master",
746+
},
747+
},
748+
},
749+
},
750+
expected: sets.New[string](),
751+
},
752+
{
753+
name: "MatchExpressions NotIn operator match success",
754+
selector: topologyv1alpha1.MemberSelector{
755+
LabelMatch: &metav1.LabelSelector{
756+
MatchExpressions: []metav1.LabelSelectorRequirement{
757+
{
758+
Key: "role",
759+
Operator: metav1.LabelSelectorOpNotIn,
760+
Values: []string{"master"},
761+
},
762+
},
763+
},
764+
},
765+
nodes: []*corev1.Node{
766+
{
767+
ObjectMeta: metav1.ObjectMeta{
768+
Name: "node1",
769+
Labels: map[string]string{
770+
"role": "worker",
771+
},
772+
},
773+
},
774+
{
775+
ObjectMeta: metav1.ObjectMeta{
776+
Name: "node2",
777+
Labels: map[string]string{
778+
"role": "master",
779+
},
780+
},
781+
},
782+
},
783+
expected: sets.New[string]("node1"),
784+
},
785+
{
786+
name: "MatchExpressions Exists operator match success",
787+
selector: topologyv1alpha1.MemberSelector{
788+
LabelMatch: &metav1.LabelSelector{
789+
MatchExpressions: []metav1.LabelSelectorRequirement{
790+
{
791+
Key: "role",
792+
Operator: metav1.LabelSelectorOpExists,
793+
},
794+
},
795+
},
796+
},
797+
nodes: []*corev1.Node{
798+
{
799+
ObjectMeta: metav1.ObjectMeta{
800+
Name: "node1",
801+
Labels: map[string]string{
802+
"role": "worker",
803+
},
804+
},
805+
},
806+
{
807+
ObjectMeta: metav1.ObjectMeta{
808+
Name: "node2",
809+
Labels: map[string]string{},
810+
},
811+
},
812+
},
813+
expected: sets.New[string]("node1"),
814+
},
815+
{
816+
name: "MatchExpressions DoesNotExist operator match success",
817+
selector: topologyv1alpha1.MemberSelector{
818+
LabelMatch: &metav1.LabelSelector{
819+
MatchExpressions: []metav1.LabelSelectorRequirement{
820+
{
821+
Key: "role",
822+
Operator: metav1.LabelSelectorOpDoesNotExist,
823+
},
824+
},
825+
},
826+
},
827+
nodes: []*corev1.Node{
828+
{
829+
ObjectMeta: metav1.ObjectMeta{
830+
Name: "node1",
831+
Labels: map[string]string{
832+
"role": "worker",
833+
},
834+
},
835+
},
836+
{
837+
ObjectMeta: metav1.ObjectMeta{
838+
Name: "node2",
839+
Labels: map[string]string{},
840+
},
841+
},
842+
},
843+
expected: sets.New[string]("node2"),
844+
},
845+
}
846+
847+
hyperNodesInfor := HyperNodesInfo{}
848+
for _, tc := range testCases {
849+
t.Run(tc.name, func(t *testing.T) {
850+
result := hyperNodesInfor.getMembers(tc.selector, tc.nodes)
851+
if !result.Equal(tc.expected) {
852+
t.Errorf("Test %s failed: Expected %v, but got %v", tc.name, tc.expected, result)
853+
}
854+
})
855+
}
856+
}

0 commit comments

Comments
 (0)