Skip to content

Commit ef77c96

Browse files
Michal Tichákknopers8
authored andcommitted
Reapply "[core] INVARIANT status introduced"
This reverts commit 0a7036b.
1 parent 6cc2cb7 commit ef77c96

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)