Skip to content

Commit cbc2247

Browse files
committed
[core] include original run number in CCDB GRP object for synthetic runs
Closes OCTRL-1006.
1 parent 97d8591 commit cbc2247

File tree

3 files changed

+317
-5
lines changed

3 files changed

+317
-5
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ INSTALL_WHAT:=$(patsubst %, install_%, $(WHAT))
7070

7171
GENERATE_DIRS := ./apricot ./coconut/cmd ./common ./common/runtype ./common/system ./core ./core/integration/ccdb ./core/integration/dcs ./core/integration/ddsched ./core/integration/kafka ./core/integration/odc ./executor ./walnut ./core/integration/trg ./core/integration/bookkeeping
7272
SRC_DIRS := ./apricot ./cmd/* ./core ./coconut ./executor ./common ./configuration ./occ/peanut ./walnut
73-
TEST_DIRS := ./apricot/local ./common/gera ./common/utils ./common/utils/safeacks ./configuration/cfgbackend ./configuration/componentcfg ./configuration/template ./core/task/sm ./core/workflow ./core/integration/odc/fairmq ./core/integration ./core/environment
73+
TEST_DIRS := ./apricot/local ./common/gera ./common/utils ./common/utils/safeacks ./configuration/cfgbackend ./configuration/componentcfg ./configuration/template ./core/task/sm ./core/workflow ./core/integration/odc/fairmq ./core/integration/ccdb ./core/integration ./core/environment
7474
GO_TEST_DIRS := ./core/repos ./core/integration/dcs ./common/monitoring
7575

7676
coverage:COVERAGE_PREFIX := ./coverage_results

core/integration/ccdb/plugin.go

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ type GeneralRunParameters struct {
6464
hbfPerTf uint32 // number of HeartBeatFrames per TimeFrame
6565
lhcPeriod string
6666
flpIdList []string
67+
originalRunNumber uint32 // in case it is a replay run, CTP needs the replayed run number (O2-5921)
6768
}
6869

6970
func parseDetectors(detectorsParam string) (detectors []string, err error) {
@@ -84,6 +85,9 @@ func getFlpIdList(envId string) (flps []string, err error) {
8485
return []string{}, err
8586
}
8687
envMan := environment.ManagerInstance()
88+
if envMan == nil {
89+
return []string{}, fmt.Errorf("environment manager not initialized")
90+
}
8791
env, err := envMan.Environment(parsedEnvId)
8892
if err != nil {
8993
return []string{}, err
@@ -265,6 +269,27 @@ func NewGRPObject(varStack map[string]string) *GeneralRunParameters {
265269
Warningf("could not parse env id, FLP list will be empty")
266270
}
267271

272+
originalRunNumber := uint64(0)
273+
originalRunNumberStr, ok := varStack["original_run_number"]
274+
if ok && len(originalRunNumberStr) > 0 {
275+
if runType != runtype.SYNTHETIC {
276+
log.WithField("partition", envId).
277+
WithField("run", runNumber).
278+
WithField("level", infologger.IL_Support).
279+
Warningf("original run number was set for a non-SYNTHETIC run, ignoring it")
280+
} else {
281+
originalRunNumber, err = strconv.ParseUint(originalRunNumberStr, 10, 32)
282+
if err != nil {
283+
log.WithError(err).
284+
WithField("partition", envId).
285+
WithField("level", infologger.IL_Support).
286+
WithField("run", runNumberStr).
287+
Errorf("cannot convert original run number '%s' to an integer", originalRunNumberStr)
288+
originalRunNumber = 0
289+
}
290+
}
291+
}
292+
268293
return &GeneralRunParameters{
269294
uint32(runNumber),
270295
runType,
@@ -278,6 +303,7 @@ func NewGRPObject(varStack map[string]string) *GeneralRunParameters {
278303
uint32(hbfPerTf),
279304
lhcPeriod,
280305
flpIds,
306+
uint32(originalRunNumber),
281307
}
282308
}
283309

@@ -342,6 +368,7 @@ func (p *Plugin) NewCcdbGrpWriteCommand(grp *GeneralRunParameters, ccdbUrl strin
342368
// o2-ecs-grp-create -h
343369
//Create GRP-ECS object and upload to the CCDB
344370
//Usage:
371+
// o2-ecs-grp-create:
345372
// -h [ --help ] Print this help message
346373
// -p [ --period ] arg data taking period
347374
// -r [ --run ] arg run number
@@ -353,13 +380,29 @@ func (p *Plugin) NewCcdbGrpWriteCommand(grp *GeneralRunParameters, ccdbUrl strin
353380
// continuous readout mode
354381
// -g [ --triggering ] arg (=FT0,FV0) comma separated list of detectors
355382
// providing a trigger
356-
// -s [ --start-time ] arg (=0) run start time in ms, now() if 0
357-
// -e [ --end-time ] arg (=0) run end time in ms, start-time+3days is
358-
// used if 0
383+
// -f [ --flps ] arg comma separated list of FLPs in the
384+
// data taking
385+
// -s [ --start-time ] arg (=0) ECS run start time in ms, now() if 0
386+
// -e [ --end-time ] arg (=0) ECS run end time in ms,
387+
// start-time+3days is used if 0
388+
// --start-time-ctp arg (=0) run start CTP time in ms, same as ECS
389+
// if not set or 0
390+
// --end-time-ctp arg (=0) run end CTP time in ms, same as ECS if
391+
// not set or 0
359392
// --ccdb-server arg (=http://alice-ccdb.cern.ch)
360393
// CCDB server for upload, local file if
361394
// empty
362-
// --refresh refresh server cache after upload
395+
// --ccdb-server-input arg CCDB server for inputs (if needed, e.g.
396+
// CTPConfig), dy default ccdb-server is
397+
// used
398+
// -m [ --meta-data ] arg metadata as key1=value1;key2=value2;..
399+
// --refresh [=arg(=async)] refresh server cache after upload:
400+
// "none" (or ""), "async" (non-blocking)
401+
// and "sync" (blocking)
402+
// --marginSOR arg (=345600000) validity at SOR
403+
// --marginEOR arg (=600000) validity margin to add after EOR
404+
// -o [ --original-run ] arg (=0) if >0, use as the source run to create
405+
// CTP/Config/Config object
363406

364407
cmd = "source /etc/profile.d/o2.sh && o2-ecs-grp-create"
365408
if len(grp.lhcPeriod) == 0 {
@@ -403,6 +446,9 @@ func (p *Plugin) NewCcdbGrpWriteCommand(grp *GeneralRunParameters, ccdbUrl strin
403446
if len(grp.flpIdList) > 0 {
404447
cmd += " -f \"" + strings.Join(grp.flpIdList, ",") + "\""
405448
}
449+
if grp.originalRunNumber > 0 {
450+
cmd += " -o " + strconv.FormatUint(uint64(grp.originalRunNumber), 10)
451+
}
406452

407453
cmd += " --ccdb-server " + ccdbUrl
408454
return
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
/*
2+
* === This file is part of ALICE O² ===
3+
*
4+
* Copyright 2025 CERN and copyright holders of ALICE O².
5+
* Author: Piotr Konopka
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 ccdb
26+
27+
import (
28+
"github.com/AliceO2Group/Control/common/runtype"
29+
. "github.com/onsi/ginkgo/v2"
30+
. "github.com/onsi/gomega"
31+
"testing"
32+
)
33+
34+
var _ = Describe("NewGRPObject", func() {
35+
var varStack map[string]string
36+
37+
BeforeEach(func() {
38+
varStack = map[string]string{
39+
"environment_id": "2oDvieFrVTi",
40+
"run_number": "123456",
41+
"run_type": "PHYSICS",
42+
"run_start_time_ms": "10000",
43+
"run_end_completion_time_ms": "20000",
44+
"trg_start_time_ms": "10100",
45+
"trg_end_time_ms": "19900",
46+
"detectors": `["ITS","TPC"]`,
47+
"pdp_n_hbf_per_tf": "128",
48+
"lhc_period": "LHC22a",
49+
"ctp_readout_enabled": "true",
50+
}
51+
})
52+
53+
It("should create a valid GRP object with all fields set", func() {
54+
grp := NewGRPObject(varStack)
55+
Expect(grp).ToNot(BeNil())
56+
Expect(grp.runNumber).To(Equal(uint32(123456)))
57+
Expect(grp.lhcPeriod).To(Equal("LHC22a"))
58+
Expect(grp.runType).To(Equal(runtype.PHYSICS))
59+
Expect(grp.runStartTimeMs).To(Equal("10000"))
60+
Expect(grp.runEndCompletionTimeMs).To(Equal("20000"))
61+
Expect(grp.trgStartTimeMs).To(Equal("10100"))
62+
Expect(grp.trgEndTimeMs).To(Equal("19900"))
63+
Expect(grp.detectors).To(ContainElements("ITS", "TPC", "TRG")) // TRG added due to ctp_readout_enabled
64+
Expect(grp.hbfPerTf).To(Equal(uint32(128)))
65+
})
66+
67+
It("should return nil when environment_id is missing", func() {
68+
delete(varStack, "environment_id")
69+
grp := NewGRPObject(varStack)
70+
Expect(grp).To(BeNil())
71+
})
72+
73+
It("should return nil when run_number is missing", func() {
74+
delete(varStack, "run_number")
75+
grp := NewGRPObject(varStack)
76+
Expect(grp).To(BeNil())
77+
})
78+
79+
It("should handle invalid run_number format", func() {
80+
varStack["run_number"] = "invalid"
81+
grp := NewGRPObject(varStack)
82+
Expect(grp).To(BeNil())
83+
})
84+
85+
It("should handle missing run_type", func() {
86+
delete(varStack, "run_type")
87+
grp := NewGRPObject(varStack)
88+
Expect(grp).ToNot(BeNil())
89+
Expect(grp.runType).To(Equal(runtype.NONE))
90+
})
91+
92+
It("should return nil when pdp_n_hbf_per_tf is missing", func() {
93+
delete(varStack, "pdp_n_hbf_per_tf")
94+
grp := NewGRPObject(varStack)
95+
Expect(grp).To(BeNil())
96+
})
97+
98+
It("should override run start time for synthetic runs and compute run end time accordingly", func() {
99+
varStack["run_type"] = "SYNTHETIC"
100+
varStack["pdp_override_run_start_time"] = "15000"
101+
grp := NewGRPObject(varStack)
102+
Expect(grp).ToNot(BeNil())
103+
Expect(grp.runStartTimeMs).To(Equal("15000"))
104+
Expect(grp.runEndCompletionTimeMs).To(Equal("25000"))
105+
})
106+
107+
It("should log a warning for run start time override in non-synthetic runs, but override it anyway", func() {
108+
// overriding is not really a strong requirement, it could be changed if requested
109+
varStack["pdp_override_run_start_time"] = "15000"
110+
grp := NewGRPObject(varStack)
111+
Expect(grp).ToNot(BeNil())
112+
Expect(grp.runStartTimeMs).To(Equal("15000"))
113+
Expect(grp.runEndCompletionTimeMs).To(Equal("25000"))
114+
})
115+
116+
It("should survive empty detectors list", func() {
117+
varStack["detectors"] = "[]"
118+
varStack["ctp_readout_enabled"] = "false"
119+
grp := NewGRPObject(varStack)
120+
Expect(grp).ToNot(BeNil())
121+
Expect(grp.detectors).To(BeEmpty())
122+
})
123+
124+
It("should return nil when there is invalid detectors JSON", func() {
125+
varStack["detectors"] = "invalid json"
126+
grp := NewGRPObject(varStack)
127+
Expect(grp).To(BeNil())
128+
})
129+
130+
It("should handle missing trg_start_time_ms and trg_end_time_ms", func() {
131+
delete(varStack, "trg_start_time_ms")
132+
delete(varStack, "trg_end_time_ms")
133+
grp := NewGRPObject(varStack)
134+
Expect(grp).ToNot(BeNil())
135+
Expect(grp.trgStartTimeMs).To(BeEmpty())
136+
Expect(grp.trgEndTimeMs).To(BeEmpty())
137+
})
138+
139+
It("should handle synthetic run with original run number", func() {
140+
varStack["run_type"] = "SYNTHETIC"
141+
varStack["original_run_number"] = "654321"
142+
grp := NewGRPObject(varStack)
143+
Expect(grp).ToNot(BeNil())
144+
Expect(grp.originalRunNumber).To(Equal(uint32(654321)))
145+
Expect(grp.runNumber).To(Equal(uint32(123456)))
146+
})
147+
148+
It("should ignore original run number for non-synthetic runs", func() {
149+
varStack["original_run_number"] = "654321"
150+
grp := NewGRPObject(varStack)
151+
Expect(grp).ToNot(BeNil())
152+
Expect(grp.originalRunNumber).To(Equal(uint32(0)))
153+
})
154+
155+
// fixme: we do not test extracting the list of FLPs, because we would need to mock an env manager with a realistic enough environment
156+
// once it is easier to mock it, we should add a test for it
157+
})
158+
159+
var _ = Describe("NewCcdbGrpWriteCommand", func() {
160+
var plugin *Plugin
161+
var grp *GeneralRunParameters
162+
163+
BeforeEach(func() {
164+
plugin = &Plugin{
165+
ccdbUrl: "http://ccdb-test:8080",
166+
}
167+
grp = &GeneralRunParameters{
168+
runNumber: 123456,
169+
runType: runtype.PHYSICS,
170+
detectors: []string{"ITS", "TPC"},
171+
runStartTimeMs: "10000",
172+
runEndCompletionTimeMs: "20000",
173+
trgStartTimeMs: "10100",
174+
trgEndTimeMs: "19900",
175+
hbfPerTf: 128,
176+
lhcPeriod: "LHC22a",
177+
}
178+
})
179+
180+
It("should create basic command with required fields", func() {
181+
cmd, err := plugin.NewCcdbGrpWriteCommand(grp, "http://ccdb-test:8080", false)
182+
Expect(err).ToNot(HaveOccurred())
183+
Expect(cmd).To(ContainSubstring(" -r 123456"))
184+
Expect(cmd).To(ContainSubstring(" -p LHC22a"))
185+
Expect(cmd).To(ContainSubstring(" -t 1")) // PHYSICS enum value
186+
Expect(cmd).To(ContainSubstring(" -n 128"))
187+
Expect(cmd).To(ContainSubstring(" -s 10000"))
188+
Expect(cmd).To(ContainSubstring(" -e 20000"))
189+
Expect(cmd).To(ContainSubstring(" --start-time-ctp 10100"))
190+
Expect(cmd).To(ContainSubstring(" --end-time-ctp 19900"))
191+
Expect(cmd).To(ContainSubstring(" --ccdb-server http://ccdb-test:8080"))
192+
})
193+
194+
It("should return an error when run number is 0", func() {
195+
grp.runNumber = 0
196+
cmd, err := plugin.NewCcdbGrpWriteCommand(grp, "http://ccdb-test:8080", false)
197+
Expect(err).To(HaveOccurred())
198+
Expect(cmd).To(BeEmpty())
199+
})
200+
201+
It("should return an error when LHC period is missing", func() {
202+
grp.lhcPeriod = ""
203+
cmd, err := plugin.NewCcdbGrpWriteCommand(grp, "http://ccdb-test:8080", false)
204+
Expect(err).To(HaveOccurred())
205+
Expect(cmd).To(BeEmpty())
206+
})
207+
208+
It("should add the refresh flag when requested", func() {
209+
cmd, err := plugin.NewCcdbGrpWriteCommand(grp, "http://ccdb-test:8080", true)
210+
Expect(err).ToNot(HaveOccurred())
211+
Expect(cmd).To(ContainSubstring(" --refresh"))
212+
})
213+
214+
It("should not add the refresh flag when not requested", func() {
215+
cmd, err := plugin.NewCcdbGrpWriteCommand(grp, "http://ccdb-test:8080", false)
216+
Expect(err).ToNot(HaveOccurred())
217+
Expect(cmd).ToNot(ContainSubstring(" --refresh"))
218+
})
219+
220+
It("should include detector lists when present", func() {
221+
grp.continuousReadoutDetectors = []string{"ITS"}
222+
grp.triggeringDetectors = []string{"TPC"}
223+
cmd, err := plugin.NewCcdbGrpWriteCommand(grp, "http://ccdb-test:8080", false)
224+
Expect(err).ToNot(HaveOccurred())
225+
Expect(cmd).To(ContainSubstring(` -d "ITS,TPC"`))
226+
Expect(cmd).To(ContainSubstring(` -c "ITS"`))
227+
Expect(cmd).To(ContainSubstring(` -g "TPC"`))
228+
})
229+
230+
It("should include FLP list when present", func() {
231+
grp.flpIdList = []string{"flp-1", "flp-2"}
232+
cmd, err := plugin.NewCcdbGrpWriteCommand(grp, "http://ccdb-test:8080", false)
233+
Expect(err).ToNot(HaveOccurred())
234+
Expect(cmd).To(ContainSubstring(` -f "flp-1,flp-2"`))
235+
})
236+
237+
It("should include original run number for synthetic runs", func() {
238+
grp.originalRunNumber = 654321
239+
cmd, err := plugin.NewCcdbGrpWriteCommand(grp, "http://ccdb-test:8080", false)
240+
Expect(err).ToNot(HaveOccurred())
241+
Expect(cmd).To(ContainSubstring(" -o 654321"))
242+
})
243+
244+
It("should skip optional fields when empty", func() {
245+
grp = &GeneralRunParameters{
246+
runNumber: 123456,
247+
lhcPeriod: "LHC22a",
248+
}
249+
cmd, err := plugin.NewCcdbGrpWriteCommand(grp, "http://ccdb-test:8080", false)
250+
Expect(err).ToNot(HaveOccurred())
251+
Expect(cmd).NotTo(ContainSubstring(" -d"))
252+
Expect(cmd).NotTo(ContainSubstring(" -c"))
253+
Expect(cmd).NotTo(ContainSubstring(" -g"))
254+
Expect(cmd).NotTo(ContainSubstring(" -f"))
255+
Expect(cmd).NotTo(ContainSubstring(" -t"))
256+
Expect(cmd).NotTo(ContainSubstring(" -s"))
257+
Expect(cmd).NotTo(ContainSubstring(" -e"))
258+
Expect(cmd).NotTo(ContainSubstring(" --start-time-ctp"))
259+
Expect(cmd).NotTo(ContainSubstring(" --end-time-ctp"))
260+
})
261+
})
262+
263+
func TestCcdbGrpPlugin(t *testing.T) {
264+
RegisterFailHandler(Fail)
265+
RunSpecs(t, "CCDB GRP integration plugin Test Suite")
266+
}

0 commit comments

Comments
 (0)