Skip to content

Commit 3cced8c

Browse files
committed
[control-operator] environment controller changes for ECS bridge compatiblity
- matching node names in cluster from partial names yaml templates - add taskID annotation to be able to filter tasks - OCC_CONTROL_PORT overwrites Spec.Control.Port of task
1 parent db63560 commit 3cced8c

10 files changed

Lines changed: 105 additions & 36 deletions

File tree

control-operator/api/v1alpha1/environment_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ type EnvironmentStatus struct {
8080

8181
type TaskReference struct {
8282
Name string `json:"name"`
83+
TaskID string `json:"taskID,omitempty"`
8384
Env []v1.EnvVar `json:"env"`
8485
ArgsCLI []string `json:"argsCLI"`
8586
ArgsTransition map[string]string `json:"argsTransition"`

control-operator/config/crd/bases/aliecs.alice.cern_environments.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4100,6 +4100,8 @@ spec:
41004100
type: array
41014101
name:
41024102
type: string
4103+
taskID:
4104+
type: string
41034105
required:
41044106
- argsCLI
41054107
- argsTransition

control-operator/ecs-manifests/environment/test_env.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ apiVersion: aliecs.alice.cern/v1alpha1
22
kind: Environment
33
metadata:
44
name: 31jdwll4xl6
5+
namespace: alice-tasks
56

67
taskTemplates:
78
tasks:
8-
mtichak-ost.cern.ch:
9+
# mtichak-ost.cern.ch:
10+
mtichak-ost:
911
# mtichak-xps-cern:
1012
# - name: readout
1113
# argsCLI:
@@ -187,7 +189,8 @@ taskTemplates:
187189
spec:
188190
state: standby
189191
tasks:
190-
mtichak-ost.cern.ch:
192+
# mtichak-ost.cern.ch:
193+
mtichak-ost:
191194
- name: readout
192195
spec:
193196
pod:

control-operator/ecs-manifests/kubernetes-manifests/readout-test.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ apiVersion: aliecs.alice.cern/v1alpha1
22
kind: Task
33
metadata:
44
name: readout
5+
namespace: alice-tasks
56
spec:
7+
nodeName: mtichak-ost.cern.ch
68
arguments:
79
chans.readout.0.address: ipc://@o2ipc-d7ef3du8ndmd7n8j7l2g
810
chans.readout.0.autoBind: "0"
@@ -25,7 +27,7 @@ spec:
2527
supplementalGroups: [10, 1105]
2628
containers:
2729
- name: readout
28-
image: gitlab-registry.cern.ch/aliceo2group/dockerfiles/alma9-flp-node:2
30+
image: gitlab-registry.cern.ch/aliceo2group/dockerfiles/alma9-flp-node/readout:1
2931
command: ["/opt/o2/bin/o2-readout-exe"]
3032
args: ["/etc/readout/readout-cfg.ini"]
3133
securityContext:
@@ -99,6 +101,7 @@ apiVersion: v1
99101
kind: ConfigMap
100102
metadata:
101103
name: readout-config
104+
namespace: alice-tasks
102105
data:
103106
readout-cfg.ini: |
104107
# Example configuration file for o2-readout-exe

control-operator/ecs-manifests/kubernetes-manifests/stfbuilder-senderoutput-test.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ apiVersion: aliecs.alice.cern/v1alpha1
22
kind: Task
33
metadata:
44
name: stfbuilder-senderoutput
5+
namespace: alice-tasks
56
spec:
7+
nodeName: mtichak-ost.cern.ch
68
arguments:
79
chans.buildertosender.0.address: ipc://@o2ipc-d7ef3du8ndmd7n8j7l30
810
chans.buildertosender.0.autoBind: "0"

control-operator/ecs-manifests/kubernetes-manifests/stfsender-test.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ apiVersion: aliecs.alice.cern/v1alpha1
22
kind: Task
33
metadata:
44
name: stfsender
5+
namespace: alice-tasks
56
spec:
7+
nodeName: mtichak-ost.cern.ch
68
arguments:
79
chans.buildertosender.0.address: ipc://@o2ipc-d7ef3du8ndmd7n8j7l30
810
chans.buildertosender.0.method: connect

control-operator/ecs-manifests/task-templates/readout-tasktemplate.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,30 @@ apiVersion: aliecs.alice.cern/v1alpha1
22
kind: TaskTemplate
33
metadata:
44
name: readout
5+
namespace: alice-tasks
56
spec:
67
envVars:
78
- O2_DETECTOR
89
- O2_PARTITION
9-
- OCC_CONTROL_PORT
1010
- O2_SYSTEM
1111
- O2_ROLE
1212
pod:
1313
hostNetwork: true
1414
hostIPC: true
15+
securityContext:
16+
fsGroup: 1100
17+
supplementalGroups: [10, 1105]
1518
containers:
1619
- name: readout
1720
image: gitlab-registry.cern.ch/aliceo2group/dockerfiles/alma9-flp-node/readout:1
1821
command: ["/opt/o2/bin/o2-readout-exe"]
22+
env:
23+
- name: OCC_CONTROL_PORT
24+
value: "47101"
1925
securityContext:
2026
privileged: true
27+
runAsUser: 1100
28+
runAsGroup: 1100
2129
volumeMounts:
2230
- name: host-shm
2331
mountPath: /dev/shm
@@ -53,4 +61,3 @@ spec:
5361
- name: gitlab-registry-secret
5462
control:
5563
mode: "direct"
56-
# port: ${OCC_CONTROL_PORT} # to be filled in

control-operator/ecs-manifests/task-templates/stfbuilder-senderoutput-tasktemplate.yaml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ apiVersion: aliecs.alice.cern/v1alpha1
22
kind: TaskTemplate
33
metadata:
44
name: stfbuilder-senderoutput
5+
namespace: alice-tasks
56
spec:
67
envVars:
78
- O2_DETECTOR
89
- O2_PARTITION
9-
- OCC_CONTROL_PORT
1010
- O2_SYSTEM
1111
- O2_ROLE
1212
- O2_INFOLOGGER_MODE
@@ -15,12 +15,19 @@ spec:
1515
hostNetwork: true
1616
hostIPC: true
1717
securityContext:
18+
fsGroup: 1100
19+
supplementalGroups: [10, 1105]
1820
containers:
1921
- name: stfbuilder
2022
image: gitlab-registry.cern.ch/aliceo2group/dockerfiles/alma9-flp-node/dd:1
2123
command: [/opt/o2/bin/StfBuilder]
24+
env:
25+
- name: OCC_CONTROL_PORT
26+
value: "47102"
2227
securityContext:
2328
privileged: true
29+
runAsUser: 1100
30+
runAsGroup: 1100
2431
volumeMounts:
2532
- name: host-shm
2633
mountPath: /dev/shm

control-operator/ecs-manifests/task-templates/stfsender-tasktemplate.yaml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ apiVersion: aliecs.alice.cern/v1alpha1
22
kind: TaskTemplate
33
metadata:
44
name: stfsender
5+
namespace: alice-tasks
56
spec:
67
envVars:
78
- O2_DETECTOR
89
- O2_PARTITION
9-
- OCC_CONTROL_PORT
1010
- O2_SYSTEM
1111
- O2_ROLE
1212
- O2_INFOLOGGER_MODE
@@ -22,12 +22,19 @@ spec:
2222
hostNetwork: true
2323
hostIPC: true
2424
securityContext:
25+
fsGroup: 1100
26+
supplementalGroups: [10, 1105]
2527
containers:
2628
- name: stfsender
2729
image: gitlab-registry.cern.ch/aliceo2group/dockerfiles/alma9-flp-node/dd:1
2830
command: ["numactl"]
31+
env:
32+
- name: OCC_CONTROL_PORT
33+
value: "47103"
2934
securityContext:
3035
privileged: true
36+
runAsUser: 1100
37+
runAsGroup: 1100
3138
volumeMounts:
3239
- name: host-shm
3340
mountPath: /dev/shm

control-operator/internal/controller/environment_controller.go

Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"maps"
3131
"slices"
3232
"strconv"
33+
"strings"
3334

3435
v1 "k8s.io/api/core/v1"
3536
k8serrors "k8s.io/apimachinery/pkg/api/errors"
@@ -53,7 +54,7 @@ type EnvironmentReconciler struct {
5354
}
5455

5556
func (r *EnvironmentReconciler) runTasksFromReferenceOnNode(ctx context.Context, taskReferences []aliecsv1alpha1.TaskReference,
56-
nodename string, req ctrl.Request, environment *aliecsv1alpha1.Environment, log logr.Logger,
57+
nodename string, resolvedNodename string, req ctrl.Request, environment *aliecsv1alpha1.Environment, log logr.Logger,
5758
) (*ctrl.Result, error) {
5859
for _, taskReference := range taskReferences {
5960
log.Info("geting stored template for task", "task", taskReference.Name)
@@ -85,31 +86,44 @@ func (r *EnvironmentReconciler) runTasksFromReferenceOnNode(ctx context.Context,
8586

8687
// TODO: regarding error handling. Is it correct to stop while handling one task? this will fail the whole deployment,
8788
// which might not be desirable outcome especially for non-critical tasks
88-
if foundIdx := slices.IndexFunc(taskReference.Env, func(envVar v1.EnvVar) bool { return envVar.Name == "OCC_CONTROL_PORT" }); foundIdx == -1 {
89-
log.Error(fmt.Errorf("didn't find OCC_CONTROL_PORT in env"), "failed to fill in env vars from template")
90-
return &reconcile.Result{}, nil
91-
} else {
92-
port, err := strconv.Atoi(taskReference.Env[foundIdx].Value)
93-
if err != nil {
94-
log.Error(fmt.Errorf("found OCC_CONTROL_PORT isn't convertible to number "), "failed to fill in env vars from template")
95-
return &reconcile.Result{}, nil
96-
}
97-
task.Spec.Control.Port = port
98-
}
89+
task.Spec.Control.Port = template.Spec.Control.Port
9990

10091
if task.Spec.Arguments == nil {
10192
task.Spec.Arguments = make(map[string]string)
10293
}
10394

10495
// TODO: check for containers!
105-
task.Spec.Pod.Containers[0].Env = append(task.Spec.Pod.Containers[0].Env, taskReference.Env...)
96+
existing := make(map[string]int, len(task.Spec.Pod.Containers[0].Env))
97+
for i, e := range task.Spec.Pod.Containers[0].Env {
98+
existing[e.Name] = i
99+
}
100+
for _, e := range taskReference.Env {
101+
if i, ok := existing[e.Name]; ok {
102+
task.Spec.Pod.Containers[0].Env[i] = e
103+
} else {
104+
task.Spec.Pod.Containers[0].Env = append(task.Spec.Pod.Containers[0].Env, e)
105+
}
106+
}
106107
task.Spec.Pod.Containers[0].Args = append(task.Spec.Pod.Containers[0].Args, taskReference.ArgsCLI...)
108+
109+
for _, envVar := range task.Spec.Pod.Containers[0].Env {
110+
if envVar.Name == "OCC_CONTROL_PORT" {
111+
if port, err := strconv.Atoi(envVar.Value); err == nil {
112+
task.Spec.Control.Port = port
113+
}
114+
break
115+
}
116+
}
107117
maps.Copy(task.Spec.Arguments, taskReference.ArgsTransition)
108118

109-
task.Spec.Pod.NodeName = nodename
119+
task.Spec.Pod.NodeName = resolvedNodename
120+
task.Spec.NodeName = resolvedNodename
110121
task.Spec.State = environment.Spec.State
111122

112123
task.Labels = labels(environment, nodename)
124+
if taskReference.TaskID != "" {
125+
task.Labels["taskID"] = taskReference.TaskID
126+
}
113127

114128
if err := controllerutil.SetControllerReference(environment, task, r.Scheme); err != nil {
115129
log.Error(err, "failed to set controller reference", "task", task.Name)
@@ -128,7 +142,7 @@ func (r *EnvironmentReconciler) runTasksFromReferenceOnNode(ctx context.Context,
128142
}
129143

130144
func (r *EnvironmentReconciler) runTaskFromDefinitionOnNode(ctx context.Context, taskDefs []aliecsv1alpha1.TaskDefinition,
131-
nodename string, req ctrl.Request, environment *aliecsv1alpha1.Environment, log logr.Logger,
145+
nodename string, resolvedNodename string, req ctrl.Request, environment *aliecsv1alpha1.Environment, log logr.Logger,
132146
) (*ctrl.Result, error) {
133147
for _, taskDef := range taskDefs {
134148
task := &aliecsv1alpha1.Task{}
@@ -148,7 +162,8 @@ func (r *EnvironmentReconciler) runTaskFromDefinitionOnNode(ctx context.Context,
148162
maps.Copy(task.Spec.Arguments, taskDef.Spec.Arguments)
149163
}
150164

151-
task.Spec.Pod.NodeName = nodename
165+
task.Spec.Pod.NodeName = resolvedNodename
166+
task.Spec.NodeName = resolvedNodename
152167
task.Spec.State = environment.Spec.State
153168

154169
task.Labels = labels(environment, nodename)
@@ -246,24 +261,31 @@ func (r *EnvironmentReconciler) Reconcile(ctx context.Context, req ctrl.Request)
246261
}
247262

248263
if environment.Status.State == "" {
264+
nodeList := &v1.NodeList{}
265+
if err := r.List(ctx, nodeList); err != nil {
266+
return ctrl.Result{}, fmt.Errorf("listing nodes: %w", err)
267+
}
268+
249269
for nodename, tasksReferences := range environment.TaskTemplates.Tasks {
250270
log.Info("creating tasks for hostname from references", "hostname", nodename, "number of tasks", len(tasksReferences))
251-
if res, err := r.checkNodeExistence(ctx, nodename); err != nil {
252-
return res, err
271+
resolvedName, err := resolveNodeName(nodeList.Items, nodename)
272+
if err != nil {
273+
return ctrl.Result{}, err
253274
}
254275

255-
if res, err := r.runTasksFromReferenceOnNode(ctx, tasksReferences, nodename, req, environment, log); res != nil {
276+
if res, err := r.runTasksFromReferenceOnNode(ctx, tasksReferences, nodename, resolvedName, req, environment, log); res != nil {
256277
return *res, err
257278
}
258279
}
259280

260281
for nodename, taskTemplates := range environment.Spec.Tasks {
261282
log.Info("creating tasks for hostname from definitions", "hostname", nodename, "number of tasks", len(taskTemplates))
262-
if res, err := r.checkNodeExistence(ctx, nodename); err != nil {
263-
return res, err
283+
resolvedName, err := resolveNodeName(nodeList.Items, nodename)
284+
if err != nil {
285+
return ctrl.Result{}, err
264286
}
265287

266-
if res, err := r.runTaskFromDefinitionOnNode(ctx, taskTemplates, nodename, req, environment, log); res != nil {
288+
if res, err := r.runTaskFromDefinitionOnNode(ctx, taskTemplates, nodename, resolvedName, req, environment, log); res != nil {
267289
return *res, err
268290
}
269291
}
@@ -311,15 +333,28 @@ func (r *EnvironmentReconciler) Reconcile(ctx context.Context, req ctrl.Request)
311333
return ctrl.Result{}, nil
312334
}
313335

314-
func (r *EnvironmentReconciler) checkNodeExistence(ctx context.Context, nodename string) (ctrl.Result, error) {
315-
node := &v1.Node{}
316-
if err := r.Get(ctx, types.NamespacedName{Name: nodename}, node); err != nil {
317-
if k8serrors.IsNotFound(err) {
318-
return ctrl.Result{}, fmt.Errorf("node %s not found in cluster", nodename)
336+
// resolveNodeName resolves a short node name (e.g. "flp001") to the full name known to
337+
// the cluster (e.g. "flp001.cern.ch") by checking whether any dot-separated component
338+
// of a node's name matches exactly. Returns an error if zero or more than one node matches.
339+
func resolveNodeName(nodes []v1.Node, nodename string) (string, error) {
340+
var matches []string
341+
for _, node := range nodes {
342+
for _, part := range strings.Split(node.Name, ".") {
343+
if part == nodename {
344+
matches = append(matches, node.Name)
345+
break
346+
}
319347
}
320-
return ctrl.Result{}, err
321348
}
322-
return ctrl.Result{}, nil
349+
350+
switch len(matches) {
351+
case 0:
352+
return "", fmt.Errorf("node %q not found in cluster", nodename)
353+
case 1:
354+
return matches[0], nil
355+
default:
356+
return "", fmt.Errorf("node name %q is ambiguous, matched: %v", nodename, matches)
357+
}
323358
}
324359

325360
func aggregateState(tasks []aliecsv1alpha1.Task, previousState string) string {

0 commit comments

Comments
 (0)