Skip to content

Commit 77bdd3a

Browse files
Michal Tichákjustonedev1
authored andcommitted
[core] INVARIANT status introduced
INVARIANT status is introduced to fix OCTRL-948. INVARIANT is overwritten by any other status in status product (X() function). We are taking account the criticality of the role when merging. If all roles are noncritical we are returning INVARIANT. If there is any critical role(s) we are merging and returning their status, which shouldn't be INVARIANT. All roles except taskRole and callrole are taken as critical
1 parent 783fc89 commit 77bdd3a

File tree

5 files changed

+554
-60
lines changed

5 files changed

+554
-60
lines changed

core/task/status.go

Lines changed: 53 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -32,47 +32,59 @@ const (
3232
PARTIAL
3333
ACTIVE
3434
UNDEPLOYABLE
35+
INVARIANT // overwritten by product with any other state. It is used only when merging non-critical states. If you merge aggregateState with only non-critical statuses you will propagate INVARIANT further
3536
)
3637

37-
var (
38-
STATUS_PRODUCT = map[Status]map[Status]Status{
39-
UNDEFINED: {
40-
UNDEFINED: UNDEFINED,
41-
INACTIVE: UNDEFINED,
42-
PARTIAL: UNDEFINED,
43-
ACTIVE: UNDEFINED,
44-
UNDEPLOYABLE: UNDEFINED,
45-
},
46-
INACTIVE: {
47-
UNDEFINED: UNDEFINED,
48-
INACTIVE: INACTIVE,
49-
PARTIAL: PARTIAL,
50-
ACTIVE: PARTIAL,
51-
UNDEPLOYABLE: UNDEPLOYABLE,
52-
},
53-
PARTIAL: {
54-
UNDEFINED: UNDEFINED,
55-
INACTIVE: PARTIAL,
56-
PARTIAL: PARTIAL,
57-
ACTIVE: PARTIAL,
58-
UNDEPLOYABLE: UNDEPLOYABLE,
59-
},
60-
ACTIVE: {
61-
UNDEFINED: UNDEFINED,
62-
INACTIVE: PARTIAL,
63-
PARTIAL: PARTIAL,
64-
ACTIVE: ACTIVE,
65-
UNDEPLOYABLE: UNDEPLOYABLE,
66-
},
67-
UNDEPLOYABLE: {
68-
UNDEFINED: UNDEFINED,
69-
INACTIVE: UNDEPLOYABLE,
70-
PARTIAL: UNDEPLOYABLE,
71-
ACTIVE: UNDEPLOYABLE,
72-
UNDEPLOYABLE: UNDEPLOYABLE,
73-
},
74-
}
75-
)
38+
var STATUS_PRODUCT = map[Status]map[Status]Status{
39+
UNDEFINED: {
40+
UNDEFINED: UNDEFINED,
41+
INACTIVE: UNDEFINED,
42+
PARTIAL: UNDEFINED,
43+
ACTIVE: UNDEFINED,
44+
UNDEPLOYABLE: UNDEFINED,
45+
INVARIANT: UNDEFINED,
46+
},
47+
INACTIVE: {
48+
UNDEFINED: UNDEFINED,
49+
INACTIVE: INACTIVE,
50+
PARTIAL: PARTIAL,
51+
ACTIVE: PARTIAL,
52+
UNDEPLOYABLE: UNDEPLOYABLE,
53+
INVARIANT: INACTIVE,
54+
},
55+
PARTIAL: {
56+
UNDEFINED: UNDEFINED,
57+
INACTIVE: PARTIAL,
58+
PARTIAL: PARTIAL,
59+
ACTIVE: PARTIAL,
60+
UNDEPLOYABLE: UNDEPLOYABLE,
61+
INVARIANT: PARTIAL,
62+
},
63+
ACTIVE: {
64+
UNDEFINED: UNDEFINED,
65+
INACTIVE: PARTIAL,
66+
PARTIAL: PARTIAL,
67+
ACTIVE: ACTIVE,
68+
UNDEPLOYABLE: UNDEPLOYABLE,
69+
INVARIANT: ACTIVE,
70+
},
71+
UNDEPLOYABLE: {
72+
UNDEFINED: UNDEFINED,
73+
INACTIVE: UNDEPLOYABLE,
74+
PARTIAL: UNDEPLOYABLE,
75+
ACTIVE: UNDEPLOYABLE,
76+
UNDEPLOYABLE: UNDEPLOYABLE,
77+
INVARIANT: UNDEPLOYABLE,
78+
},
79+
INVARIANT: {
80+
UNDEFINED: UNDEFINED,
81+
INACTIVE: INACTIVE,
82+
PARTIAL: PARTIAL,
83+
ACTIVE: ACTIVE,
84+
UNDEPLOYABLE: UNDEPLOYABLE,
85+
INVARIANT: INVARIANT,
86+
},
87+
}
7688

7789
func (s Status) String() string {
7890
names := []string{
@@ -81,8 +93,9 @@ func (s Status) String() string {
8193
"PARTIAL",
8294
"ACTIVE",
8395
"UNDEPLOYABLE",
96+
"INVARIANT",
8497
}
85-
if s > UNDEPLOYABLE {
98+
if s > INVARIANT {
8699
return "UNDEFINED"
87100
}
88101
return names[s]

core/task/status_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* === This file is part of ALICE O² ===
3+
*
4+
* Copyright 2025 CERN and copyright holders of ALICE O².
5+
* Author: Teo Mrnjavac <teo.mrnjavac@cern.ch>
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*
20+
* In applying this license CERN does not waive the privileges and
21+
* immunities granted to it by virtue of its status as an
22+
* Intergovernmental Organization or submit itself to any jurisdiction.
23+
*/
24+
25+
package task
26+
27+
import (
28+
. "github.com/onsi/ginkgo/v2"
29+
. "github.com/onsi/gomega"
30+
)
31+
32+
var _ = Describe("task status", func() {
33+
When("product of all statuses with UNDEFINED is done", func() {
34+
undefined := Status(UNDEFINED)
35+
It("should be UNDEFINED for all", func() {
36+
Expect(Status(UNDEFINED)).To(Equal(undefined.X(UNDEFINED)))
37+
Expect(Status(UNDEFINED)).To(Equal(undefined.X(INACTIVE)))
38+
Expect(Status(UNDEFINED)).To(Equal(undefined.X(PARTIAL)))
39+
Expect(Status(UNDEFINED)).To(Equal(undefined.X(ACTIVE)))
40+
Expect(Status(UNDEFINED)).To(Equal(undefined.X(UNDEPLOYABLE)))
41+
Expect(Status(UNDEFINED)).To(Equal(undefined.X(INVARIANT)))
42+
})
43+
})
44+
When("product of all statuses with INACTIVE is done", func() {
45+
inactive := Status(INACTIVE)
46+
It("should have different results", func() {
47+
Expect(Status(UNDEFINED)).To(Equal(inactive.X(UNDEFINED)))
48+
Expect(Status(INACTIVE)).To(Equal(inactive.X(INACTIVE)))
49+
Expect(Status(PARTIAL)).To(Equal(inactive.X(PARTIAL)))
50+
Expect(Status(PARTIAL)).To(Equal(inactive.X(ACTIVE)))
51+
Expect(Status(UNDEPLOYABLE)).To(Equal(inactive.X(UNDEPLOYABLE)))
52+
Expect(Status(INACTIVE)).To(Equal(inactive.X(INVARIANT)))
53+
})
54+
})
55+
When("product of all statuses with PARTIAL is done", func() {
56+
partial := Status(PARTIAL)
57+
It("should have different results", func() {
58+
Expect(Status(UNDEFINED)).To(Equal(partial.X(UNDEFINED)))
59+
Expect(Status(PARTIAL)).To(Equal(partial.X(INACTIVE)))
60+
Expect(Status(PARTIAL)).To(Equal(partial.X(PARTIAL)))
61+
Expect(Status(PARTIAL)).To(Equal(partial.X(ACTIVE)))
62+
Expect(Status(UNDEPLOYABLE)).To(Equal(partial.X(UNDEPLOYABLE)))
63+
Expect(Status(PARTIAL)).To(Equal(partial.X(INVARIANT)))
64+
})
65+
})
66+
When("product of all statuses with ACTIVE is done", func() {
67+
active := Status(ACTIVE)
68+
It("should have different results", func() {
69+
Expect(Status(UNDEFINED)).To(Equal(active.X(UNDEFINED)))
70+
Expect(Status(PARTIAL)).To(Equal(active.X(INACTIVE)))
71+
Expect(Status(PARTIAL)).To(Equal(active.X(PARTIAL)))
72+
Expect(Status(ACTIVE)).To(Equal(active.X(ACTIVE)))
73+
Expect(Status(UNDEPLOYABLE)).To(Equal(active.X(UNDEPLOYABLE)))
74+
Expect(Status(ACTIVE)).To(Equal(active.X(INVARIANT)))
75+
})
76+
})
77+
When("product of all statuses with UNDEPLOYABLE is done", func() {
78+
undeployable := Status(UNDEPLOYABLE)
79+
It("should be UNDEPLOYABLE unless UNDEFINED", func() {
80+
Expect(Status(UNDEFINED)).To(Equal(undeployable.X(UNDEFINED)))
81+
Expect(Status(UNDEPLOYABLE)).To(Equal(undeployable.X(INACTIVE)))
82+
Expect(Status(UNDEPLOYABLE)).To(Equal(undeployable.X(PARTIAL)))
83+
Expect(Status(UNDEPLOYABLE)).To(Equal(undeployable.X(ACTIVE)))
84+
Expect(Status(UNDEPLOYABLE)).To(Equal(undeployable.X(UNDEPLOYABLE)))
85+
Expect(Status(UNDEPLOYABLE)).To(Equal(undeployable.X(INVARIANT)))
86+
})
87+
})
88+
When("product of all statuses with INVARIANT is done", func() {
89+
invariant := Status(INVARIANT)
90+
It("it should be the status the product was done with", func() {
91+
Expect(Status(UNDEFINED)).To(Equal(invariant.X(UNDEFINED)))
92+
Expect(Status(INACTIVE)).To(Equal(invariant.X(INACTIVE)))
93+
Expect(Status(PARTIAL)).To(Equal(invariant.X(PARTIAL)))
94+
Expect(Status(ACTIVE)).To(Equal(invariant.X(ACTIVE)))
95+
Expect(Status(UNDEPLOYABLE)).To(Equal(invariant.X(UNDEPLOYABLE)))
96+
Expect(Status(INVARIANT)).To(Equal(invariant.X(INVARIANT)))
97+
})
98+
})
99+
When("String() of Status is called", func() {
100+
It("should return string representation of a status", func() {
101+
Expect("UNDEFINED").To(Equal(Status(UNDEFINED).String()))
102+
Expect("INACTIVE").To(Equal(Status(INACTIVE).String()))
103+
Expect("PARTIAL").To(Equal(Status(PARTIAL).String()))
104+
Expect("ACTIVE").To(Equal(Status(ACTIVE).String()))
105+
Expect("UNDEPLOYABLE").To(Equal(Status(UNDEPLOYABLE).String()))
106+
Expect("INVARIANT").To(Equal(Status(INVARIANT).String()))
107+
Expect("UNDEFINED").To(Equal(Status(255).String()))
108+
})
109+
})
110+
})

core/task/task_suite_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* === This file is part of ALICE O² ===
3+
*
4+
* Copyright 2025 CERN and copyright holders of ALICE O².
5+
* Author: Teo Mrnjavac <teo.mrnjavac@cern.ch>
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*
20+
* In applying this license CERN does not waive the privileges and
21+
* immunities granted to it by virtue of its status as an
22+
* Intergovernmental Organization or submit itself to any jurisdiction.
23+
*/
24+
25+
package task
26+
27+
import (
28+
"testing"
29+
30+
. "github.com/onsi/ginkgo/v2"
31+
. "github.com/onsi/gomega"
32+
)
33+
34+
func TestTask(t *testing.T) {
35+
RegisterFailHandler(Fail)
36+
RunSpecs(t, "Task Test Suite")
37+
}

core/workflow/safestatus.go

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,52 +25,78 @@
2525
package workflow
2626

2727
import (
28+
"strconv"
2829
"strings"
2930
"sync"
3031

3132
"github.com/AliceO2Group/Control/core/task"
3233
"github.com/sirupsen/logrus"
34+
"github.com/spf13/viper"
3335
)
3436

3537
type SafeStatus struct {
3638
mu sync.RWMutex
3739
status task.Status
3840
}
3941

42+
func reportTraceRoles(roles []Role, status task.Status) {
43+
if viper.GetBool("veryVerbose") {
44+
stati := make([]string, len(roles))
45+
critical := make([]string, len(roles))
46+
names := make([]string, len(roles))
47+
for i, role := range roles {
48+
stati[i] = role.GetStatus().String()
49+
names[i] = role.GetName()
50+
if taskR, isTaskRole := role.(*taskRole); isTaskRole {
51+
critical[i] = strconv.FormatBool(taskR.IsCritical())
52+
} else if callR, isCallRole := role.(*callRole); isCallRole {
53+
critical[i] = strconv.FormatBool(callR.IsCritical())
54+
} else {
55+
critical[i] = strconv.FormatBool(true)
56+
}
57+
}
58+
log.WithFields(logrus.Fields{
59+
"statuses": strings.Join(stati, ", "),
60+
"critical": strings.Join(critical, ", "),
61+
"names": strings.Join(names, ", "),
62+
"aggregated": status.String(),
63+
}).
64+
Trace("aggregating statuses")
65+
}
66+
}
67+
68+
// role that are not taskRole or callRole are critical by default
4069
func aggregateStatus(roles []Role) (status task.Status) {
4170
if len(roles) == 0 {
4271
status = task.UNDEFINED
4372
return
4473
}
45-
stati := make([]string, len(roles))
46-
for i, role := range roles {
47-
stati[i] = role.GetStatus().String()
48-
}
4974

50-
status = roles[0].GetStatus()
51-
if len(roles) > 1 {
52-
for _, c := range roles[1:] {
53-
if status == task.UNDEFINED {
54-
log.WithFields(logrus.Fields{
55-
"statuses": strings.Join(stati, ", "),
56-
"aggregated": status.String(),
57-
}).
58-
Trace("aggregating statuses")
75+
status = task.INVARIANT
76+
for _, role := range roles {
77+
if status == task.UNDEFINED {
78+
break
79+
}
5980

60-
return
81+
if taskR, isTaskRole := role.(*taskRole); isTaskRole {
82+
if !taskR.IsCritical() {
83+
continue
84+
}
85+
} else if callR, isCallRole := role.(*callRole); isCallRole {
86+
if !callR.IsCritical() {
87+
continue
6188
}
62-
status = status.X(c.GetStatus())
6389
}
90+
status = status.X(role.GetStatus())
6491
}
65-
log.WithFields(logrus.Fields{
66-
"statuses": strings.Join(stati, ", "),
67-
"aggregated": status.String(),
68-
}).
69-
Trace("aggregating statuses")
92+
93+
reportTraceRoles(roles, status)
7094

7195
return
7296
}
7397

98+
// TODO: this function is prime candidate for refactoring. The reason being that it mostly ignores status argument
99+
// for merging, moreover it also does not use status of role from argument. Both of these behaivour are counter-intuitive.
74100
func (t *SafeStatus) merge(s task.Status, r Role) {
75101
t.mu.Lock()
76102
defer t.mu.Unlock()

0 commit comments

Comments
 (0)