From ac891595502983d6046de51b08c97e541e9ef560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Tich=C3=A1k?= Date: Tue, 1 Apr 2025 14:40:00 +0200 Subject: [PATCH] [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 --- core/task/status.go | 93 ++++++---- core/task/status_test.go | 110 +++++++++++ core/task/task_suite_test.go | 37 ++++ core/workflow/safestatus.go | 66 +++++-- core/workflow/safestatus_test.go | 308 +++++++++++++++++++++++++++++++ 5 files changed, 554 insertions(+), 60 deletions(-) create mode 100644 core/task/status_test.go create mode 100644 core/task/task_suite_test.go create mode 100644 core/workflow/safestatus_test.go diff --git a/core/task/status.go b/core/task/status.go index 3bfb35353..f21df2960 100644 --- a/core/task/status.go +++ b/core/task/status.go @@ -32,47 +32,59 @@ const ( PARTIAL ACTIVE UNDEPLOYABLE + 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 ) -var ( - STATUS_PRODUCT = map[Status]map[Status]Status{ - UNDEFINED: { - UNDEFINED: UNDEFINED, - INACTIVE: UNDEFINED, - PARTIAL: UNDEFINED, - ACTIVE: UNDEFINED, - UNDEPLOYABLE: UNDEFINED, - }, - INACTIVE: { - UNDEFINED: UNDEFINED, - INACTIVE: INACTIVE, - PARTIAL: PARTIAL, - ACTIVE: PARTIAL, - UNDEPLOYABLE: UNDEPLOYABLE, - }, - PARTIAL: { - UNDEFINED: UNDEFINED, - INACTIVE: PARTIAL, - PARTIAL: PARTIAL, - ACTIVE: PARTIAL, - UNDEPLOYABLE: UNDEPLOYABLE, - }, - ACTIVE: { - UNDEFINED: UNDEFINED, - INACTIVE: PARTIAL, - PARTIAL: PARTIAL, - ACTIVE: ACTIVE, - UNDEPLOYABLE: UNDEPLOYABLE, - }, - UNDEPLOYABLE: { - UNDEFINED: UNDEFINED, - INACTIVE: UNDEPLOYABLE, - PARTIAL: UNDEPLOYABLE, - ACTIVE: UNDEPLOYABLE, - UNDEPLOYABLE: UNDEPLOYABLE, - }, - } -) +var STATUS_PRODUCT = map[Status]map[Status]Status{ + UNDEFINED: { + UNDEFINED: UNDEFINED, + INACTIVE: UNDEFINED, + PARTIAL: UNDEFINED, + ACTIVE: UNDEFINED, + UNDEPLOYABLE: UNDEFINED, + INVARIANT: UNDEFINED, + }, + INACTIVE: { + UNDEFINED: UNDEFINED, + INACTIVE: INACTIVE, + PARTIAL: PARTIAL, + ACTIVE: PARTIAL, + UNDEPLOYABLE: UNDEPLOYABLE, + INVARIANT: INACTIVE, + }, + PARTIAL: { + UNDEFINED: UNDEFINED, + INACTIVE: PARTIAL, + PARTIAL: PARTIAL, + ACTIVE: PARTIAL, + UNDEPLOYABLE: UNDEPLOYABLE, + INVARIANT: PARTIAL, + }, + ACTIVE: { + UNDEFINED: UNDEFINED, + INACTIVE: PARTIAL, + PARTIAL: PARTIAL, + ACTIVE: ACTIVE, + UNDEPLOYABLE: UNDEPLOYABLE, + INVARIANT: ACTIVE, + }, + UNDEPLOYABLE: { + UNDEFINED: UNDEFINED, + INACTIVE: UNDEPLOYABLE, + PARTIAL: UNDEPLOYABLE, + ACTIVE: UNDEPLOYABLE, + UNDEPLOYABLE: UNDEPLOYABLE, + INVARIANT: UNDEPLOYABLE, + }, + INVARIANT: { + UNDEFINED: UNDEFINED, + INACTIVE: INACTIVE, + PARTIAL: PARTIAL, + ACTIVE: ACTIVE, + UNDEPLOYABLE: UNDEPLOYABLE, + INVARIANT: INVARIANT, + }, +} func (s Status) String() string { names := []string{ @@ -81,8 +93,9 @@ func (s Status) String() string { "PARTIAL", "ACTIVE", "UNDEPLOYABLE", + "INVARIANT", } - if s > UNDEPLOYABLE { + if s > INVARIANT { return "UNDEFINED" } return names[s] diff --git a/core/task/status_test.go b/core/task/status_test.go new file mode 100644 index 000000000..b4c429230 --- /dev/null +++ b/core/task/status_test.go @@ -0,0 +1,110 @@ +/* + * === This file is part of ALICE O² === + * + * Copyright 2025 CERN and copyright holders of ALICE O². + * Author: Teo Mrnjavac + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * In applying this license CERN does not waive the privileges and + * immunities granted to it by virtue of its status as an + * Intergovernmental Organization or submit itself to any jurisdiction. + */ + +package task + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("task status", func() { + When("product of all statuses with UNDEFINED is done", func() { + undefined := Status(UNDEFINED) + It("should be UNDEFINED for all", func() { + Expect(Status(UNDEFINED)).To(Equal(undefined.X(UNDEFINED))) + Expect(Status(UNDEFINED)).To(Equal(undefined.X(INACTIVE))) + Expect(Status(UNDEFINED)).To(Equal(undefined.X(PARTIAL))) + Expect(Status(UNDEFINED)).To(Equal(undefined.X(ACTIVE))) + Expect(Status(UNDEFINED)).To(Equal(undefined.X(UNDEPLOYABLE))) + Expect(Status(UNDEFINED)).To(Equal(undefined.X(INVARIANT))) + }) + }) + When("product of all statuses with INACTIVE is done", func() { + inactive := Status(INACTIVE) + It("should have different results", func() { + Expect(Status(UNDEFINED)).To(Equal(inactive.X(UNDEFINED))) + Expect(Status(INACTIVE)).To(Equal(inactive.X(INACTIVE))) + Expect(Status(PARTIAL)).To(Equal(inactive.X(PARTIAL))) + Expect(Status(PARTIAL)).To(Equal(inactive.X(ACTIVE))) + Expect(Status(UNDEPLOYABLE)).To(Equal(inactive.X(UNDEPLOYABLE))) + Expect(Status(INACTIVE)).To(Equal(inactive.X(INVARIANT))) + }) + }) + When("product of all statuses with PARTIAL is done", func() { + partial := Status(PARTIAL) + It("should have different results", func() { + Expect(Status(UNDEFINED)).To(Equal(partial.X(UNDEFINED))) + Expect(Status(PARTIAL)).To(Equal(partial.X(INACTIVE))) + Expect(Status(PARTIAL)).To(Equal(partial.X(PARTIAL))) + Expect(Status(PARTIAL)).To(Equal(partial.X(ACTIVE))) + Expect(Status(UNDEPLOYABLE)).To(Equal(partial.X(UNDEPLOYABLE))) + Expect(Status(PARTIAL)).To(Equal(partial.X(INVARIANT))) + }) + }) + When("product of all statuses with ACTIVE is done", func() { + active := Status(ACTIVE) + It("should have different results", func() { + Expect(Status(UNDEFINED)).To(Equal(active.X(UNDEFINED))) + Expect(Status(PARTIAL)).To(Equal(active.X(INACTIVE))) + Expect(Status(PARTIAL)).To(Equal(active.X(PARTIAL))) + Expect(Status(ACTIVE)).To(Equal(active.X(ACTIVE))) + Expect(Status(UNDEPLOYABLE)).To(Equal(active.X(UNDEPLOYABLE))) + Expect(Status(ACTIVE)).To(Equal(active.X(INVARIANT))) + }) + }) + When("product of all statuses with UNDEPLOYABLE is done", func() { + undeployable := Status(UNDEPLOYABLE) + It("should be UNDEPLOYABLE unless UNDEFINED", func() { + Expect(Status(UNDEFINED)).To(Equal(undeployable.X(UNDEFINED))) + Expect(Status(UNDEPLOYABLE)).To(Equal(undeployable.X(INACTIVE))) + Expect(Status(UNDEPLOYABLE)).To(Equal(undeployable.X(PARTIAL))) + Expect(Status(UNDEPLOYABLE)).To(Equal(undeployable.X(ACTIVE))) + Expect(Status(UNDEPLOYABLE)).To(Equal(undeployable.X(UNDEPLOYABLE))) + Expect(Status(UNDEPLOYABLE)).To(Equal(undeployable.X(INVARIANT))) + }) + }) + When("product of all statuses with INVARIANT is done", func() { + invariant := Status(INVARIANT) + It("it should be the status the product was done with", func() { + Expect(Status(UNDEFINED)).To(Equal(invariant.X(UNDEFINED))) + Expect(Status(INACTIVE)).To(Equal(invariant.X(INACTIVE))) + Expect(Status(PARTIAL)).To(Equal(invariant.X(PARTIAL))) + Expect(Status(ACTIVE)).To(Equal(invariant.X(ACTIVE))) + Expect(Status(UNDEPLOYABLE)).To(Equal(invariant.X(UNDEPLOYABLE))) + Expect(Status(INVARIANT)).To(Equal(invariant.X(INVARIANT))) + }) + }) + When("String() of Status is called", func() { + It("should return string representation of a status", func() { + Expect("UNDEFINED").To(Equal(Status(UNDEFINED).String())) + Expect("INACTIVE").To(Equal(Status(INACTIVE).String())) + Expect("PARTIAL").To(Equal(Status(PARTIAL).String())) + Expect("ACTIVE").To(Equal(Status(ACTIVE).String())) + Expect("UNDEPLOYABLE").To(Equal(Status(UNDEPLOYABLE).String())) + Expect("INVARIANT").To(Equal(Status(INVARIANT).String())) + Expect("UNDEFINED").To(Equal(Status(255).String())) + }) + }) +}) diff --git a/core/task/task_suite_test.go b/core/task/task_suite_test.go new file mode 100644 index 000000000..c5f0cf618 --- /dev/null +++ b/core/task/task_suite_test.go @@ -0,0 +1,37 @@ +/* + * === This file is part of ALICE O² === + * + * Copyright 2025 CERN and copyright holders of ALICE O². + * Author: Teo Mrnjavac + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * In applying this license CERN does not waive the privileges and + * immunities granted to it by virtue of its status as an + * Intergovernmental Organization or submit itself to any jurisdiction. + */ + +package task + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestTask(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Task Test Suite") +} diff --git a/core/workflow/safestatus.go b/core/workflow/safestatus.go index 33f1a2049..044e86be6 100644 --- a/core/workflow/safestatus.go +++ b/core/workflow/safestatus.go @@ -25,11 +25,13 @@ package workflow import ( + "strconv" "strings" "sync" "github.com/AliceO2Group/Control/core/task" "github.com/sirupsen/logrus" + "github.com/spf13/viper" ) type SafeStatus struct { @@ -37,40 +39,64 @@ type SafeStatus struct { status task.Status } +func reportTraceRoles(roles []Role, status task.Status) { + if viper.GetBool("veryVerbose") { + stati := make([]string, len(roles)) + critical := make([]string, len(roles)) + names := make([]string, len(roles)) + for i, role := range roles { + stati[i] = role.GetStatus().String() + names[i] = role.GetName() + if taskR, isTaskRole := role.(*taskRole); isTaskRole { + critical[i] = strconv.FormatBool(taskR.IsCritical()) + } else if callR, isCallRole := role.(*callRole); isCallRole { + critical[i] = strconv.FormatBool(callR.IsCritical()) + } else { + critical[i] = strconv.FormatBool(true) + } + } + log.WithFields(logrus.Fields{ + "statuses": strings.Join(stati, ", "), + "critical": strings.Join(critical, ", "), + "names": strings.Join(names, ", "), + "aggregated": status.String(), + }). + Trace("aggregating statuses") + } +} + +// role that are not taskRole or callRole are critical by default func aggregateStatus(roles []Role) (status task.Status) { if len(roles) == 0 { status = task.UNDEFINED return } - stati := make([]string, len(roles)) - for i, role := range roles { - stati[i] = role.GetStatus().String() - } - status = roles[0].GetStatus() - if len(roles) > 1 { - for _, c := range roles[1:] { - if status == task.UNDEFINED { - log.WithFields(logrus.Fields{ - "statuses": strings.Join(stati, ", "), - "aggregated": status.String(), - }). - Trace("aggregating statuses") + status = task.INVARIANT + for _, role := range roles { + if status == task.UNDEFINED { + break + } - return + if taskR, isTaskRole := role.(*taskRole); isTaskRole { + if !taskR.IsCritical() { + continue + } + } else if callR, isCallRole := role.(*callRole); isCallRole { + if !callR.IsCritical() { + continue } - status = status.X(c.GetStatus()) } + status = status.X(role.GetStatus()) } - log.WithFields(logrus.Fields{ - "statuses": strings.Join(stati, ", "), - "aggregated": status.String(), - }). - Trace("aggregating statuses") + + reportTraceRoles(roles, status) return } +// TODO: this function is prime candidate for refactoring. The reason being that it mostly ignores status argument +// for merging, moreover it also does not use status of role from argument. Both of these behaivour are counter-intuitive. func (t *SafeStatus) merge(s task.Status, r Role) { t.mu.Lock() defer t.mu.Unlock() diff --git a/core/workflow/safestatus_test.go b/core/workflow/safestatus_test.go new file mode 100644 index 000000000..0e2306cc7 --- /dev/null +++ b/core/workflow/safestatus_test.go @@ -0,0 +1,308 @@ +/* + * === This file is part of ALICE O² === + * + * Copyright 2025 CERN and copyright holders of ALICE O². + * Author: Teo Mrnjavac + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * In applying this license CERN does not waive the privileges and + * immunities granted to it by virtue of its status as an + * Intergovernmental Organization or submit itself to any jurisdiction. + */ + +package workflow + +import ( + "github.com/AliceO2Group/Control/core/task" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/spf13/viper" +) + +var _ = Describe("safe status merge", func() { + viper.Set("veryVerbose", true) + When("Task and Call roles are used", func() { + It("status should be overwritten", func() { + { + cr := &callRole{ + roleBase: roleBase{status: SafeStatus{status: task.PARTIAL}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: true}, + } + cr.status.merge(task.ACTIVE, cr) + Expect(cr.GetStatus()).To(Equal(task.Status(task.ACTIVE))) + } + { + tr := &taskRole{ + roleBase: roleBase{status: SafeStatus{status: task.PARTIAL}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: true}, + } + tr.status.merge(task.INACTIVE, tr) + Expect(tr.GetStatus()).To(Equal(task.Status(task.INACTIVE))) + } + }) + }) + + When("Aggregate role is merged with UNDEFINED status", func() { + It("becomes UNDEFINED", func() { + ar := &aggregatorRole{roleBase: roleBase{status: SafeStatus{status: task.ACTIVE}}} + ar.status.merge(task.UNDEFINED, ar) + Expect(ar.GetStatus()).To(Equal(task.Status(task.UNDEFINED))) + }) + }) + + When("Empty aggregate role is merged with any status (except UNDEFINED) status", func() { + It("becomes UNDEFINED", func() { + ar := &aggregatorRole{roleBase: roleBase{status: SafeStatus{status: task.INACTIVE}}} + ar.status.merge(task.ACTIVE, ar) + Expect(ar.GetStatus()).To(Equal(task.Status(task.UNDEFINED))) + }) + }) + + When("Aggregate role is merged with the same status", func() { + It("does not change it's status", func() { + ar := &aggregatorRole{roleBase: roleBase{status: SafeStatus{status: task.ACTIVE}}} + ar.status.merge(task.ACTIVE, ar) + Expect(ar.GetStatus()).To(Equal(task.Status(task.ACTIVE))) + }) + }) + + When("Aggregator role has ACTIVE subroles", func() { + It("becomes ACTIVE", func() { + ar := &aggregatorRole{ + roleBase: roleBase{status: SafeStatus{status: task.UNDEFINED}}, + aggregator: aggregator{ + []Role{ + &taskRole{ + roleBase: roleBase{status: SafeStatus{status: task.ACTIVE}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: true}, + }, + &taskRole{ + roleBase: roleBase{status: SafeStatus{status: task.ACTIVE}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: true}, + }, + }, + }, + } + // SafeStatus.Merge basically ignores status argument in a lot of cases, so it does not matter what we use here + // and in following tests + ar.status.merge(task.INACTIVE, ar) + Expect(ar.GetStatus()).To(Equal(task.Status(task.ACTIVE))) + }) + }) + + When("Aggregator role has one ACTIVE subrole", func() { + It("becomes PARTIAL from INACTIVE", func() { + ar := &aggregatorRole{ + roleBase: roleBase{status: SafeStatus{status: task.UNDEFINED}}, + aggregator: aggregator{ + []Role{ + &taskRole{ + roleBase: roleBase{status: SafeStatus{status: task.ACTIVE}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: true}, + }, + &taskRole{ + roleBase: roleBase{status: SafeStatus{status: task.INACTIVE}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: true}, + }, + }, + }, + } + ar.status.merge(task.INACTIVE, ar) + Expect(ar.GetStatus()).To(Equal(task.Status(task.PARTIAL))) + }) + }) + + When("Aggregator role has one ACTIVE subrole", func() { + It("becomes PARTIAL from PARTIAL", func() { + ar := &aggregatorRole{ + roleBase: roleBase{status: SafeStatus{status: task.UNDEFINED}}, + aggregator: aggregator{ + []Role{ + &taskRole{ + roleBase: roleBase{status: SafeStatus{status: task.ACTIVE}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: true}, + }, + &taskRole{ + roleBase: roleBase{status: SafeStatus{status: task.PARTIAL}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: true}, + }, + }, + }, + } + ar.status.merge(task.INACTIVE, ar) + Expect(ar.GetStatus()).To(Equal(task.Status(task.PARTIAL))) + }) + }) + + When("Aggregator role has one ACTIVE subrole", func() { + It("becomes UNDEPLOYABLE from UNDEPLOYABLE", func() { + ar := &aggregatorRole{ + roleBase: roleBase{status: SafeStatus{status: task.UNDEFINED}}, + aggregator: aggregator{ + []Role{ + &taskRole{ + roleBase: roleBase{status: SafeStatus{status: task.ACTIVE}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: true}, + }, + &taskRole{ + roleBase: roleBase{status: SafeStatus{status: task.UNDEPLOYABLE}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: true}, + }, + }, + }, + } + ar.status.merge(task.INACTIVE, ar) + Expect(ar.GetStatus()).To(Equal(task.Status(task.UNDEPLOYABLE))) + }) + }) + + When("Aggregator role has one ACTIVE subrole", func() { + It("becomes UNDEPLOYABLE from UNDEPLOYABLE", func() { + ar := &aggregatorRole{ + roleBase: roleBase{status: SafeStatus{status: task.UNDEFINED}}, + aggregator: aggregator{ + []Role{ + &taskRole{ + roleBase: roleBase{status: SafeStatus{status: task.ACTIVE}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: true}, + }, + &callRole{ + roleBase: roleBase{status: SafeStatus{status: task.UNDEPLOYABLE}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: true}, + }, + }, + }, + } + ar.status.merge(task.INACTIVE, ar) + Expect(ar.GetStatus()).To(Equal(task.Status(task.UNDEPLOYABLE))) + }) + }) + + When("Aggregator role has one ACTIVE subrole", func() { + It("becomes UNDEFINED from UNDEFINED", func() { + ar := &aggregatorRole{ + roleBase: roleBase{status: SafeStatus{status: task.UNDEFINED}}, + aggregator: aggregator{ + []Role{ + &callRole{ + roleBase: roleBase{status: SafeStatus{status: task.UNDEFINED}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: true}, + }, + &taskRole{ + roleBase: roleBase{status: SafeStatus{status: task.ACTIVE}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: true}, + }, + }, + }, + } + ar.status.merge(task.INACTIVE, ar) + Expect(ar.GetStatus()).To(Equal(task.Status(task.UNDEFINED))) + }) + }) + + When("Aggregator role has no critical roles", func() { + It("becomes INVARIANT regardless of subrole statuses", func() { + ar := &aggregatorRole{ + roleBase: roleBase{status: SafeStatus{status: task.UNDEFINED}}, + aggregator: aggregator{ + []Role{ + &taskRole{ + roleBase: roleBase{status: SafeStatus{status: task.ACTIVE}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: false}, + }, + &callRole{ + roleBase: roleBase{status: SafeStatus{status: task.UNDEPLOYABLE}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: false}, + }, + }, + }, + } + ar.status.merge(task.INACTIVE, ar) + Expect(ar.GetStatus()).To(Equal(task.Status(task.INVARIANT))) + }) + }) + + When("Aggregator role has one ACTIVE critical subrole and one INACTIVE non critical role", func() { + It("becomes ACTIVE", func() { + ar := &aggregatorRole{ + roleBase: roleBase{status: SafeStatus{status: task.UNDEFINED}}, + aggregator: aggregator{ + []Role{ + &callRole{ + roleBase: roleBase{status: SafeStatus{status: task.ACTIVE}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: true}, + }, + &taskRole{ + roleBase: roleBase{status: SafeStatus{status: task.INACTIVE}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: false}, + }, + }, + }, + } + ar.status.merge(task.INACTIVE, ar) + Expect(ar.GetStatus()).To(Equal(task.Status(task.ACTIVE))) + }) + }) + + When("Aggregator role has one ACTIVE critical subrole and one INACTIVE non critical role and one ACTIVE aggregator role", func() { + It("becomes ACTIVE", func() { + ar := &aggregatorRole{ + roleBase: roleBase{status: SafeStatus{status: task.UNDEFINED}}, + aggregator: aggregator{ + []Role{ + &callRole{ + roleBase: roleBase{status: SafeStatus{status: task.ACTIVE}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: true}, + }, + &taskRole{ + roleBase: roleBase{status: SafeStatus{status: task.INACTIVE}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: false}, + }, + &aggregatorRole{ + roleBase: roleBase{status: SafeStatus{status: task.ACTIVE}}, + }, + }, + }, + } + ar.status.merge(task.INACTIVE, ar) + Expect(ar.GetStatus()).To(Equal(task.Status(task.ACTIVE))) + }) + }) + + When("Aggregator role has one ACTIVE critical subrole and one INACTIVE non critical role and one INACTIVE aggregator role", func() { + It("becomes PARTIAL", func() { + ar := &aggregatorRole{ + roleBase: roleBase{status: SafeStatus{status: task.UNDEFINED}}, + aggregator: aggregator{ + []Role{ + &callRole{ + roleBase: roleBase{status: SafeStatus{status: task.ACTIVE}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: true}, + }, + &taskRole{ + roleBase: roleBase{status: SafeStatus{status: task.INACTIVE}}, + Traits: task.Traits{Trigger: "", Await: "", Timeout: "", Critical: false}, + }, + &aggregatorRole{ + roleBase: roleBase{status: SafeStatus{status: task.INACTIVE}}, + }, + }, + }, + } + ar.status.merge(task.INACTIVE, ar) + Expect(ar.GetStatus()).To(Equal(task.Status(task.PARTIAL))) + }) + }) +})