Skip to content

Commit 792c7fb

Browse files
Merge pull request #1285 from Tal-or/scheduler_ha
CNF-17778: CNF-17779: schedulercontroller: High availability by default
2 parents 6c5ab88 + 4edb028 commit 792c7fb

7 files changed

Lines changed: 111 additions & 38 deletions

api/v1/numaresourcesscheduler_defaults.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ const (
2828
defaultSchedulerInformer = SchedulerInformerDedicated
2929
defaultCacheResyncDetection = CacheResyncDetectionRelaxed
3030
defaultScoringStrategy = LeastAllocated
31-
defaultReplicas = int32(1)
3231
)
3332

3433
func SetDefaults_NUMAResourcesSchedulerSpec(spec *NUMAResourcesSchedulerSpec) {
@@ -54,8 +53,6 @@ func SetDefaults_NUMAResourcesSchedulerSpec(spec *NUMAResourcesSchedulerSpec) {
5453
Type: defaultScoringStrategy,
5554
}
5655
}
57-
if spec.Replicas == nil {
58-
replicas := defaultReplicas
59-
spec.Replicas = &replicas
60-
}
56+
// do not set replicas default value
57+
// it will be computed later
6158
}

api/v1/numaresourcesscheduler_defaults_test.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ func TestSetDefaults_NUMAResourcesSchedulerSpec(t *testing.T) {
6262
ScoringStrategy: &ScoringStrategyParams{
6363
Type: scoringStrategyType,
6464
},
65-
Replicas: ptr.To[int32](defaultReplicas),
6665
},
6766
},
6867
{
@@ -83,7 +82,6 @@ func TestSetDefaults_NUMAResourcesSchedulerSpec(t *testing.T) {
8382
ScoringStrategy: &ScoringStrategyParams{
8483
Type: scoringStrategyType,
8584
},
86-
Replicas: ptr.To[int32](defaultReplicas),
8785
},
8886
},
8987
{
@@ -107,7 +105,6 @@ func TestSetDefaults_NUMAResourcesSchedulerSpec(t *testing.T) {
107105
ScoringStrategy: &ScoringStrategyParams{
108106
Type: scoringStrategyType,
109107
},
110-
Replicas: ptr.To[int32](defaultReplicas),
111108
},
112109
},
113110
{
@@ -132,7 +129,6 @@ func TestSetDefaults_NUMAResourcesSchedulerSpec(t *testing.T) {
132129
ScoringStrategy: &ScoringStrategyParams{
133130
Type: scoringStrategyType,
134131
},
135-
Replicas: ptr.To[int32](defaultReplicas),
136132
},
137133
},
138134
{
@@ -157,7 +153,6 @@ func TestSetDefaults_NUMAResourcesSchedulerSpec(t *testing.T) {
157153
ScoringStrategy: &ScoringStrategyParams{
158154
Type: scoringStrategyType,
159155
},
160-
Replicas: ptr.To[int32](defaultReplicas),
161156
},
162157
},
163158
{
@@ -180,7 +175,6 @@ func TestSetDefaults_NUMAResourcesSchedulerSpec(t *testing.T) {
180175
SchedulerInformer: &schedInformer,
181176
CacheResyncDetection: &cacheResyncDetection,
182177
ScoringStrategy: &scoringStrategyCustom,
183-
Replicas: ptr.To[int32](defaultReplicas),
184178
},
185179
},
186180
{
@@ -274,7 +268,6 @@ func TestSetDefaults_NUMAResourcesSchedulerSpec(t *testing.T) {
274268
ScoringStrategy: &ScoringStrategyParams{
275269
Type: scoringStrategyType,
276270
},
277-
Replicas: ptr.To[int32](defaultReplicas),
278271
},
279272
},
280273
{

api/v1/numaresourcesscheduler_normalize_test.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ func TestNUMAResourcesSchedulerSpecNormalize(t *testing.T) {
6262
ScoringStrategy: &ScoringStrategyParams{
6363
Type: scoringStrategyType,
6464
},
65-
Replicas: ptr.To[int32](defaultReplicas),
6665
},
6766
},
6867
{
@@ -83,7 +82,6 @@ func TestNUMAResourcesSchedulerSpecNormalize(t *testing.T) {
8382
ScoringStrategy: &ScoringStrategyParams{
8483
Type: scoringStrategyType,
8584
},
86-
Replicas: ptr.To[int32](defaultReplicas),
8785
},
8886
},
8987
{
@@ -107,7 +105,6 @@ func TestNUMAResourcesSchedulerSpecNormalize(t *testing.T) {
107105
ScoringStrategy: &ScoringStrategyParams{
108106
Type: scoringStrategyType,
109107
},
110-
Replicas: ptr.To[int32](defaultReplicas),
111108
},
112109
},
113110
{
@@ -132,7 +129,6 @@ func TestNUMAResourcesSchedulerSpecNormalize(t *testing.T) {
132129
ScoringStrategy: &ScoringStrategyParams{
133130
Type: scoringStrategyType,
134131
},
135-
Replicas: ptr.To[int32](defaultReplicas),
136132
},
137133
},
138134
{
@@ -157,7 +153,6 @@ func TestNUMAResourcesSchedulerSpecNormalize(t *testing.T) {
157153
ScoringStrategy: &ScoringStrategyParams{
158154
Type: scoringStrategyType,
159155
},
160-
Replicas: ptr.To[int32](defaultReplicas),
161156
},
162157
},
163158
{
@@ -180,7 +175,6 @@ func TestNUMAResourcesSchedulerSpecNormalize(t *testing.T) {
180175
SchedulerInformer: &schedInformer,
181176
CacheResyncDetection: &cacheResyncDetection,
182177
ScoringStrategy: &scoringStrategyCustom,
183-
Replicas: ptr.To[int32](defaultReplicas),
184178
},
185179
},
186180
{
@@ -274,7 +268,6 @@ func TestNUMAResourcesSchedulerSpecNormalize(t *testing.T) {
274268
ScoringStrategy: &ScoringStrategyParams{
275269
Type: scoringStrategyType,
276270
},
277-
Replicas: ptr.To[int32](defaultReplicas),
278271
},
279272
},
280273
{

cmd/main.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ import (
6161
"github.com/openshift-kni/numaresources-operator/pkg/hash"
6262
"github.com/openshift-kni/numaresources-operator/pkg/images"
6363
rtemetricsmanifests "github.com/openshift-kni/numaresources-operator/pkg/metrics/manifests/monitor"
64-
"github.com/openshift-kni/numaresources-operator/pkg/numaresourcesscheduler/controlplane"
6564
schedmanifests "github.com/openshift-kni/numaresources-operator/pkg/numaresourcesscheduler/manifests/sched"
6665
rtestate "github.com/openshift-kni/numaresources-operator/pkg/objectstate/rte"
6766
rteupdate "github.com/openshift-kni/numaresources-operator/pkg/objectupdate/rte"
@@ -159,7 +158,7 @@ func (pa *Params) FromFlags() {
159158
flag.BoolVar(&pa.enableMCPCondsForward, "enable-mcp-conds-fwd", pa.enableMCPCondsForward, "enable MCP Status Condition forwarding")
160159
flag.StringVar(&pa.image.Exporter, "image-exporter", pa.image.Exporter, "use this image as default for the RTE")
161160
flag.StringVar(&pa.image.Scheduler, "image-scheduler", pa.image.Scheduler, "use this image as default for the scheduler")
162-
flag.BoolVar(&pa.enableReplicasDetect, "detect-replicas", pa.enableReplicasDetect, "autodetect optimal replica count")
161+
flag.BoolVar(&pa.enableReplicasDetect, "detect-replicas", pa.enableReplicasDetect, "autodetect optimal replica count.(DEPRECATED) autodetect enabled by default and should be configured from the NUMAResourcesScheduler CR")
163162

164163
flag.Parse()
165164

@@ -312,11 +311,6 @@ func main() {
312311
}
313312

314313
if params.enableScheduler {
315-
info := controlplane.Defaults()
316-
if params.enableReplicasDetect {
317-
info = controlplane.Discover(ctx)
318-
}
319-
320314
schedMf, err := schedmanifests.GetManifests(namespace)
321315
if err != nil {
322316
klog.ErrorS(err, "unable to load the Scheduler manifests")
@@ -329,7 +323,6 @@ func main() {
329323
Scheme: mgr.GetScheme(),
330324
SchedulerManifests: schedMf,
331325
Namespace: namespace,
332-
AutodetectReplicas: info.NodeCount,
333326
}).SetupWithManager(mgr); err != nil {
334327
klog.ErrorS(err, "unable to create controller", "controller", "NUMAResourcesScheduler")
335328
os.Exit(1)

internal/controller/numaresourcesscheduler_controller.go

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package controller
1919
import (
2020
"context"
2121
"fmt"
22+
"sigs.k8s.io/controller-runtime/pkg/handler"
2223
"time"
2324

2425
appsv1 "k8s.io/api/apps/v1"
@@ -27,8 +28,10 @@ import (
2728
apiequality "k8s.io/apimachinery/pkg/api/equality"
2829
apierrors "k8s.io/apimachinery/pkg/api/errors"
2930
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31+
"k8s.io/apimachinery/pkg/labels"
3032
"k8s.io/apimachinery/pkg/runtime"
3133
"k8s.io/klog/v2"
34+
"k8s.io/utils/ptr"
3235
ctrl "sigs.k8s.io/controller-runtime"
3336
"sigs.k8s.io/controller-runtime/pkg/builder"
3437
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -60,7 +63,6 @@ type NUMAResourcesSchedulerReconciler struct {
6063
Scheme *runtime.Scheme
6164
SchedulerManifests schedmanifests.Manifests
6265
Namespace string
63-
AutodetectReplicas int
6466
}
6567

6668
//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles,verbs=*
@@ -126,6 +128,18 @@ func (r *NUMAResourcesSchedulerReconciler) SetupWithManager(mgr ctrl.Manager) er
126128
!apiequality.Semantic.DeepEqual(e.ObjectNew.GetAnnotations(), e.ObjectOld.GetAnnotations())
127129
},
128130
}
131+
nodesPredicate := predicate.Funcs{
132+
// we only care about cases when nodes are getting created or deleted
133+
CreateFunc: func(e event.TypedCreateEvent[client.Object]) bool {
134+
return true
135+
},
136+
DeleteFunc: func(e event.TypedDeleteEvent[client.Object]) bool {
137+
return true
138+
},
139+
UpdateFunc: func(e event.TypedUpdateEvent[client.Object]) bool {
140+
return false
141+
},
142+
}
129143

130144
return ctrl.NewControllerManagedBy(mgr).
131145
For(&nropv1.NUMAResourcesScheduler{}).
@@ -134,9 +148,25 @@ func (r *NUMAResourcesSchedulerReconciler) SetupWithManager(mgr ctrl.Manager) er
134148
Owns(&corev1.ServiceAccount{}, builder.WithPredicates(p)).
135149
Owns(&corev1.ConfigMap{}, builder.WithPredicates(p)).
136150
Owns(&appsv1.Deployment{}, builder.WithPredicates(p)).
151+
Watches(&corev1.Node{}, handler.EnqueueRequestsFromMapFunc(r.nodeToNUMAResourcesScheduler),
152+
builder.WithPredicates(nodesPredicate)).
137153
Complete(r)
138154
}
139155

156+
func (r *NUMAResourcesSchedulerReconciler) nodeToNUMAResourcesScheduler(ctx context.Context, object client.Object) []reconcile.Request {
157+
var requests []reconcile.Request
158+
nross := &nropv1.NUMAResourcesSchedulerList{}
159+
if err := r.List(ctx, nross); err != nil {
160+
klog.ErrorS(err, "failed to List NUMAResourcesScheduler")
161+
}
162+
for _, instance := range nross.Items {
163+
requests = append(requests, reconcile.Request{NamespacedName: client.ObjectKey{
164+
Name: instance.Name,
165+
}})
166+
}
167+
return requests
168+
}
169+
140170
func (r *NUMAResourcesSchedulerReconciler) reconcileResource(ctx context.Context, instance *nropv1.NUMAResourcesScheduler) (reconcile.Result, string, error) {
141171
schedStatus, err := r.syncNUMASchedulerResources(ctx, instance)
142172
if err != nil {
@@ -171,13 +201,22 @@ func isDeploymentRunning(ctx context.Context, c client.Client, key nropv1.Namesp
171201
return false, nil
172202
}
173203

174-
func (r *NUMAResourcesSchedulerReconciler) computeSchedulerReplicas(schedSpec nropv1.NUMAResourcesSchedulerSpec) *int32 {
175-
// the api validation/normalization layer must ensure this value is != nil
176-
if *schedSpec.Replicas >= 0 { // 0 is legit value to disable the deployment
177-
return schedSpec.Replicas
204+
func (r *NUMAResourcesSchedulerReconciler) computeSchedulerReplicas(ctx context.Context, schedSpec nropv1.NUMAResourcesSchedulerSpec) (*int32, error) {
205+
// do not autodetect if explicitly set by the user
206+
if schedSpec.Replicas != nil {
207+
return schedSpec.Replicas, nil
208+
}
209+
nodeList := &corev1.NodeList{}
210+
if err := r.Client.List(ctx, nodeList, &client.ListOptions{
211+
LabelSelector: labels.SelectorFromSet(map[string]string{
212+
"node-role.kubernetes.io/control-plane": "",
213+
}),
214+
}); err != nil {
215+
return schedSpec.Replicas, err
178216
}
179-
v := int32(r.AutodetectReplicas)
180-
return &v
217+
replicas := ptr.To(int32(len(nodeList.Items)))
218+
klog.InfoS("autodetect scheduler replicas", "replicas", *replicas)
219+
return replicas, nil
181220
}
182221

183222
func (r *NUMAResourcesSchedulerReconciler) syncNUMASchedulerResources(ctx context.Context, instance *nropv1.NUMAResourcesScheduler) (nropv1.NUMAResourcesSchedulerStatus, error) {
@@ -186,6 +225,11 @@ func (r *NUMAResourcesSchedulerReconciler) syncNUMASchedulerResources(ctx contex
186225

187226
schedSpec := instance.Spec.Normalize()
188227
cacheResyncPeriod := unpackAPIResyncPeriod(schedSpec.CacheResyncPeriod)
228+
replicas, err := r.computeSchedulerReplicas(ctx, schedSpec)
229+
if err != nil {
230+
return nropv1.NUMAResourcesSchedulerStatus{}, fmt.Errorf("failed to compute scheduler replicas: %w", err)
231+
}
232+
schedSpec.Replicas = replicas
189233
params := configParamsFromSchedSpec(schedSpec, cacheResyncPeriod, r.Namespace)
190234

191235
schedName, ok := schedstate.SchedulerNameFromObject(r.SchedulerManifests.ConfigMap)
@@ -207,7 +251,7 @@ func (r *NUMAResourcesSchedulerReconciler) syncNUMASchedulerResources(ctx contex
207251
},
208252
}
209253

210-
r.SchedulerManifests.Deployment.Spec.Replicas = r.computeSchedulerReplicas(schedSpec)
254+
r.SchedulerManifests.Deployment.Spec.Replicas = schedSpec.Replicas
211255
klog.V(4).InfoS("using scheduler replicas", "replicas", *r.SchedulerManifests.Deployment.Spec.Replicas)
212256
// TODO: if replicas doesn't make sense (autodetect disabled and user set impossible value) then we
213257
// should set a degraded state

internal/controller/numaresourcesscheduler_controller_test.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package controller
1818

1919
import (
2020
"context"
21+
"fmt"
2122
"time"
2223

2324
"github.com/onsi/ginkgo/v2"
@@ -33,6 +34,7 @@ import (
3334
"k8s.io/apimachinery/pkg/runtime"
3435
"k8s.io/client-go/kubernetes/scheme"
3536
"k8s.io/klog/v2"
37+
"k8s.io/utils/ptr"
3638
"sigs.k8s.io/controller-runtime/pkg/client"
3739
"sigs.k8s.io/controller-runtime/pkg/client/fake"
3840
"sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -93,11 +95,14 @@ var _ = ginkgo.Describe("Test NUMAResourcesScheduler Reconcile", func() {
9395
ginkgo.Context("with correct NRS CR", func() {
9496
var nrs *nropv1.NUMAResourcesScheduler
9597
var reconciler *NUMAResourcesSchedulerReconciler
98+
numOfMasters := 3
9699

97100
ginkgo.BeforeEach(func() {
98101
var err error
99102
nrs = testobjs.NewNUMAResourcesScheduler("numaresourcesscheduler", "some/url:latest", testSchedulerName, 11*time.Second)
100-
reconciler, err = NewFakeNUMAResourcesSchedulerReconciler(nrs)
103+
initObjects := []runtime.Object{nrs}
104+
initObjects = append(initObjects, fakeNodes(numOfMasters, 3)...)
105+
reconciler, err = NewFakeNUMAResourcesSchedulerReconciler(initObjects...)
101106
gomega.Expect(err).ToNot(gomega.HaveOccurred())
102107
})
103108

@@ -585,10 +590,20 @@ var _ = ginkgo.Describe("Test NUMAResourcesScheduler Reconcile", func() {
585590
})
586591

587592
ginkgo.It("should set the leader election resource parameters by default", func() {
593+
nrs := nrs.DeepCopy()
594+
nrs.Spec.Replicas = ptr.To(int32(1))
595+
gomega.Eventually(reconciler.Client.Update).WithArguments(context.TODO(), nrs).WithPolling(30 * time.Second).WithTimeout(5 * time.Minute).Should(gomega.Succeed())
596+
597+
_, err := reconciler.Reconcile(context.TODO(), reconcile.Request{NamespacedName: client.ObjectKeyFromObject(nrs)})
598+
gomega.Expect(err).ToNot(gomega.HaveOccurred())
599+
expectLeaderElectParams(reconciler.Client, false, testNamespace, nrosched.LeaderElectionResourceName)
600+
})
601+
602+
ginkgo.It("should set the leader election resource parameters to true default", func() {
588603
key := client.ObjectKeyFromObject(nrs)
589604
_, err := reconciler.Reconcile(context.TODO(), reconcile.Request{NamespacedName: key})
590605
gomega.Expect(err).ToNot(gomega.HaveOccurred())
591-
expectLeaderElectParams(reconciler.Client, false, testNamespace, nrosched.LeaderElectionResourceName)
606+
expectLeaderElectParams(reconciler.Client, true, testNamespace, nrosched.LeaderElectionResourceName)
592607
})
593608

594609
ginkgo.DescribeTable("should set the leader election resource parameters depending on replica count", func(replicas int32, expectedEnabled bool) {
@@ -605,6 +620,16 @@ var _ = ginkgo.Describe("Test NUMAResourcesScheduler Reconcile", func() {
605620
ginkgo.Entry("replicas=1", int32(1), false),
606621
ginkgo.Entry("replicas=3", int32(3), true),
607622
)
623+
624+
ginkgo.It("should detect replicas number by default when spec.Replicas is unset", func() {
625+
key := client.ObjectKeyFromObject(nrs)
626+
_, err := reconciler.Reconcile(context.TODO(), reconcile.Request{NamespacedName: key})
627+
gomega.Expect(err).ToNot(gomega.HaveOccurred())
628+
629+
dp := &appsv1.Deployment{}
630+
gomega.Expect(reconciler.Client.Get(context.TODO(), client.ObjectKey{Namespace: testNamespace, Name: "secondary-scheduler"}, dp)).To(gomega.Succeed())
631+
gomega.Expect(*dp.Spec.Replicas).To(gomega.Equal(int32(numOfMasters)), "number of replicas is different than number of control-planes nodes; want=%d got=%d", numOfMasters, *dp.Spec.Replicas)
632+
})
608633
})
609634
})
610635

@@ -695,3 +720,28 @@ func expectLeaderElectParams(cli client.Client, enabled bool, resourceNamespace,
695720
gomega.Expect(cfg.LeaderElection.ResourceNamespace).To(gomega.Equal(resourceNamespace))
696721
gomega.Expect(cfg.LeaderElection.ResourceName).To(gomega.Equal(resourceName))
697722
}
723+
724+
func fakeNodes(numOfMasters, numOfWorkers int) []runtime.Object {
725+
var nodes []runtime.Object
726+
for i := range numOfMasters {
727+
nodes = append(nodes, &corev1.Node{
728+
ObjectMeta: metav1.ObjectMeta{
729+
Name: fmt.Sprintf("master-node-%d", i+1),
730+
Labels: map[string]string{
731+
"node-role.kubernetes.io/control-plane": "",
732+
},
733+
},
734+
})
735+
}
736+
for i := range numOfWorkers {
737+
nodes = append(nodes, &corev1.Node{
738+
ObjectMeta: metav1.ObjectMeta{
739+
Name: fmt.Sprintf("worker-node-%d", i+1),
740+
Labels: map[string]string{
741+
"node-role.kubernetes.io/worker": "",
742+
},
743+
},
744+
})
745+
}
746+
return nodes
747+
}

0 commit comments

Comments
 (0)