Skip to content

Commit 95c68f7

Browse files
hervedombyaMonPote
authored andcommitted
feat: add pod scheduling spec
ci: update Docker workflow to always push images ci: update Docker workflow to conditionally push images based on main branch events; refactor scheduling logic in ScalityUIComponent controller
1 parent 2a0efd2 commit 95c68f7

File tree

10 files changed

+2178
-0
lines changed

10 files changed

+2178
-0
lines changed

api/v1alpha1/scalityui_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ type ScalityUISpec struct {
3838
Networks *UINetworks `json:"networks,omitempty"`
3939
Auth *AuthConfig `json:"auth,omitempty"`
4040
ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"`
41+
42+
// Scheduling defines pod scheduling constraints for the UI deployment
43+
Scheduling *PodSchedulingSpec `json:"scheduling,omitempty"`
4144
}
4245

4346
// Themes defines the various themes supported by the UI.

api/v1alpha1/scalityuicomponent_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ type ScalityUIComponentSpec struct {
4040
// ImagePullSecrets is a list of references to secrets in the same namespace to use for pulling any images
4141
// +optional
4242
ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"`
43+
44+
// Scheduling defines pod scheduling constraints for the UI component deployment
45+
Scheduling *PodSchedulingSpec `json:"scheduling,omitempty"`
4346
}
4447

4548
// ScalityUIComponentStatus defines the observed state of ScalityUIComponent

api/v1alpha1/scheduling_types.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
Copyright 2025.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha1
18+
19+
import (
20+
corev1 "k8s.io/api/core/v1"
21+
)
22+
23+
// PodSchedulingSpec defines pod scheduling constraints that can be applied to workloads
24+
type PodSchedulingSpec struct {
25+
// Tolerations allows the pods to be scheduled on nodes with matching taints
26+
// +optional
27+
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
28+
29+
// NodeSelector constrains the pods to run only on nodes with specific labels
30+
// +optional
31+
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
32+
33+
// Affinity defines scheduling constraints using node/pod affinity rules
34+
// +optional
35+
Affinity *corev1.Affinity `json:"affinity,omitempty"`
36+
37+
// PriorityClassName indicates the pod's priority class
38+
// +optional
39+
PriorityClassName string `json:"priorityClassName,omitempty"`
40+
}

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 44 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/ui.scality.com_scalityuicomponents.yaml

Lines changed: 982 additions & 0 deletions
Large diffs are not rendered by default.

config/crd/bases/ui.scality.com_scalityuis.yaml

Lines changed: 982 additions & 0 deletions
Large diffs are not rendered by default.

internal/controller/scalityui/controller_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,57 @@ var _ = Describe("ScalityUI Shell Features", func() {
131131
})
132132
})
133133

134+
Describe("Shell UI Pod Scheduling", func() {
135+
It("should apply tolerations from scheduling spec to deployment", func() {
136+
By("Setting up a ScalityUI with tolerations")
137+
reconciler := NewScalityUIReconcilerForTest(k8sClient, k8sClient.Scheme())
138+
139+
// Get current UI and add scheduling constraints
140+
currentUI := &uiv1alpha1.ScalityUI{}
141+
Expect(k8sClient.Get(ctx, clusterScopedName, currentUI)).To(Succeed())
142+
143+
currentUI.Spec.Scheduling = &uiv1alpha1.PodSchedulingSpec{
144+
Tolerations: []corev1.Toleration{
145+
{
146+
Key: "node-role.kubernetes.io/bootstrap",
147+
Operator: "Exists",
148+
Effect: "NoSchedule",
149+
},
150+
{
151+
Key: "node-role.kubernetes.io/infra",
152+
Operator: "Exists",
153+
Effect: "NoSchedule",
154+
},
155+
},
156+
}
157+
158+
Expect(k8sClient.Update(ctx, currentUI)).To(Succeed())
159+
160+
By("Reconciling the deployment with new scheduling constraints")
161+
_, err := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: clusterScopedName})
162+
Expect(err).NotTo(HaveOccurred())
163+
164+
By("Verifying tolerations are applied to the deployment")
165+
deployment := &appsv1.Deployment{}
166+
deploymentName := types.NamespacedName{Name: uiAppName + "-deployment", Namespace: getOperatorNamespace()}
167+
Eventually(func() error {
168+
return k8sClient.Get(ctx, deploymentName, deployment)
169+
}, eventuallyTimeout, eventuallyInterval).Should(Succeed())
170+
171+
Expect(deployment.Spec.Template.Spec.Tolerations).To(HaveLen(2))
172+
173+
// Check first toleration
174+
Expect(deployment.Spec.Template.Spec.Tolerations[0].Key).To(Equal("node-role.kubernetes.io/bootstrap"))
175+
Expect(deployment.Spec.Template.Spec.Tolerations[0].Operator).To(Equal(corev1.TolerationOperator("Exists")))
176+
Expect(deployment.Spec.Template.Spec.Tolerations[0].Effect).To(Equal(corev1.TaintEffect("NoSchedule")))
177+
178+
// Check second toleration
179+
Expect(deployment.Spec.Template.Spec.Tolerations[1].Key).To(Equal("node-role.kubernetes.io/infra"))
180+
Expect(deployment.Spec.Template.Spec.Tolerations[1].Operator).To(Equal(corev1.TolerationOperator("Exists")))
181+
Expect(deployment.Spec.Template.Spec.Tolerations[1].Effect).To(Equal(corev1.TaintEffect("NoSchedule")))
182+
})
183+
})
184+
134185
Describe("Shell UI Customization Features", func() {
135186
It("should allow customization of shell branding and navigation", func() {
136187
By("Deploying the Shell UI initially")

internal/controller/scalityui/deployment.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ func newScalityUIDeploymentReconciler(cr ScalityUI, currentState State) reconcil
7171
},
7272
}
7373
}
74+
75+
// Add tolerations from CR spec
76+
if cr.Spec.Scheduling != nil && len(cr.Spec.Scheduling.Tolerations) > 0 {
77+
spec.Tolerations = cr.Spec.Scheduling.Tolerations
78+
}
79+
7480
return spec
7581
},
7682
Containers: []resources.GenericContainer{

internal/controller/scalityuicomponent/controller.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ func (r *ScalityUIComponentReconciler) Reconcile(ctx context.Context, req ctrl.R
189189
}
190190
}
191191

192+
// Apply tolerations from CR spec
193+
if scalityUIComponent.Spec.Scheduling != nil && len(scalityUIComponent.Spec.Scheduling.Tolerations) > 0 {
194+
deployment.Spec.Template.Spec.Tolerations = scalityUIComponent.Spec.Scheduling.Tolerations
195+
}
196+
192197
return nil
193198
})
194199

internal/controller/scalityuicomponent/controller_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,68 @@ var _ = Describe("ScalityUIComponent Controller", func() {
198198
Expect(updatedDeployment.Spec.Template.Spec.ImagePullSecrets).To(BeEmpty())
199199
})
200200

201+
It("should apply tolerations from scheduling spec to deployment", func() {
202+
By("Reconciling the created resource initially")
203+
controllerReconciler := NewScalityUIComponentReconciler(k8sClient, k8sClient.Scheme())
204+
mockFetcher := &MockConfigFetcher{
205+
ConfigContent: `{
206+
"kind": "UIModule",
207+
"apiVersion": "v1alpha1",
208+
"metadata": {"kind": "TestKind"},
209+
"spec": {
210+
"remoteEntryPath": "/remoteEntry.js",
211+
"publicPath": "/test-public/",
212+
"version": "1.2.3"
213+
}
214+
}`,
215+
}
216+
controllerReconciler.ConfigFetcher = mockFetcher
217+
218+
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName})
219+
Expect(err).NotTo(HaveOccurred())
220+
221+
By("Adding scheduling constraints to the resource")
222+
fetchedResource := &uiv1alpha1.ScalityUIComponent{}
223+
Expect(k8sClient.Get(ctx, typeNamespacedName, fetchedResource)).To(Succeed())
224+
225+
fetchedResource.Spec.Scheduling = &uiv1alpha1.PodSchedulingSpec{
226+
Tolerations: []corev1.Toleration{
227+
{
228+
Key: "node-role.kubernetes.io/bootstrap",
229+
Operator: "Exists",
230+
Effect: "NoSchedule",
231+
},
232+
{
233+
Key: "node-role.kubernetes.io/infra",
234+
Operator: "Exists",
235+
Effect: "NoSchedule",
236+
},
237+
},
238+
}
239+
240+
Expect(k8sClient.Update(ctx, fetchedResource)).To(Succeed())
241+
242+
By("Reconciling again to apply scheduling constraints")
243+
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName})
244+
Expect(err).NotTo(HaveOccurred())
245+
246+
By("Verifying tolerations are applied to the deployment")
247+
deployment := &appsv1.Deployment{}
248+
Expect(k8sClient.Get(ctx, deploymentNamespacedName, deployment)).To(Succeed())
249+
250+
Expect(deployment.Spec.Template.Spec.Tolerations).To(HaveLen(2))
251+
252+
// Check first toleration
253+
Expect(deployment.Spec.Template.Spec.Tolerations[0].Key).To(Equal("node-role.kubernetes.io/bootstrap"))
254+
Expect(deployment.Spec.Template.Spec.Tolerations[0].Operator).To(Equal(corev1.TolerationOperator("Exists")))
255+
Expect(deployment.Spec.Template.Spec.Tolerations[0].Effect).To(Equal(corev1.TaintEffect("NoSchedule")))
256+
257+
// Check second toleration
258+
Expect(deployment.Spec.Template.Spec.Tolerations[1].Key).To(Equal("node-role.kubernetes.io/infra"))
259+
Expect(deployment.Spec.Template.Spec.Tolerations[1].Operator).To(Equal(corev1.TolerationOperator("Exists")))
260+
Expect(deployment.Spec.Template.Spec.Tolerations[1].Effect).To(Equal(corev1.TaintEffect("NoSchedule")))
261+
})
262+
201263
It("should requeue if Deployment is not ready", func() {
202264
By("Reconciling the created resource")
203265
mockFetcher := &MockConfigFetcher{}

0 commit comments

Comments
 (0)