Skip to content

Commit 7580440

Browse files
committed
Add teuthology-nightly-cadence (and trigger) plus teuthology-runner
Signed-off-by: deepssin <deepssin@redhat.com>
1 parent 4bbd04e commit 7580440

11 files changed

Lines changed: 1252 additions & 0 deletions

File tree

ceph-release-containers/build/Jenkinsfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ pipeline {
102102
./make-manifest-list.py --version ${VERSION}
103103
else
104104
./make-manifest-list.py
105+
fi
105106
'''
106107
}
107108
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
teuthology-nightly-cadence
2+
===========================
3+
4+
Thin Jenkins jobs that expand **daily/weekly cadence** locally into **SUITE_RUNS_JSON** (partition /
5+
subset per suite) and invoke **teuthology-runner**.
6+
7+
- ``teuthology-nightly-cadence``: builds JSON from ``cadenceSteps`` / day-of-year in this folder's Jenkinsfile, then triggers the runner job (default ``teuthology-runner``; set **TEUTHOLOGY_RUNNER_JOB_NAME** for a test copy).
8+
- ``teuthology-nightly-cadence-trigger``: JJB ``timed`` block with ``TZ=Etc/UTC`` (same pattern as
9+
``ceph-dev-cron``). Schedules echo ``qa/crontab/teuthology-cronjobs``: weekday **06:00** UTC → **daily**
10+
cadence; **Sunday 20:00** UTC → **weekly** cadence (cf. ``00 20 * * 0`` weekly main runs there).
11+
``wait: false``. Edit ``triggers`` in the YAML to tune times.
12+
13+
Scripts and Shaman/suite logic live under ``teuthology-runner/``. Ensure the **teuthology-runner**
14+
job exists in Jenkins (see ``teuthology-runner/config/definitions/teuthology-runner.yml``).
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
/**
2+
* teuthology-nightly-cadence: expands daily/weekly cadence into SUITE_RUNS_JSON and invokes teuthology-runner.
3+
* Cadence definitions live here (partition/subset).
4+
*/
5+
import java.util.Calendar
6+
import java.util.TimeZone
7+
8+
pipeline {
9+
agent { label 'built-in' }
10+
options {
11+
timestamps()
12+
timeout(time: 8, unit: 'HOURS')
13+
buildDiscarder(logRotator(numToKeepStr: '25'))
14+
}
15+
parameters {
16+
string(name: 'CEPH_BUILD_BRANCH', defaultValue: 'main', description: 'ceph-build branch for teuthology-runner SCM.')
17+
string(name: 'TEUTHOLOGY_RUNNER_JOB_NAME', defaultValue: 'teuthology-runner', description: 'Jenkins job name for the runner pipeline.')
18+
string(name: 'AGENT_LABEL', defaultValue: 'teuthology', description: 'Agent label for teuthology-runner.')
19+
choice(name: 'CADENCE', choices: ['daily', 'weekly'], description: 'daily ≈ nop+smoke; weekly ≈ ceph.git teuthology-cronjobs style.')
20+
choice(name: 'CEPH_BRANCH', choices: ['main', 'tentacle', 'squid', 'reef'], description: 'Ceph branch.')
21+
string(name: 'CEPH_REPO', defaultValue: 'https://github.com/ceph/ceph.git', description: 'Ceph git URL.')
22+
string(name: 'CEPH_SHA1', defaultValue: '', description: 'Optional SHA1. Empty = resolve from Shaman for SHAMAN_WAIT_PLATFORMS, then pass explicitly to teuthology-runner.')
23+
booleanParam(name: 'USE_WORKSPACE_TEUTHOLOGY', defaultValue: true, description: 'Clone teuthology in runner workspace.')
24+
string(name: 'TEUTHOLOGY_REPO_URL', defaultValue: 'https://github.com/ceph/teuthology.git', description: 'Teuthology repo.')
25+
string(name: 'TEUTHOLOGY_BRANCH', defaultValue: 'main', description: 'Teuthology branch.')
26+
string(name: 'TEUTHOLOGY_SCRIPT_DIR', defaultValue: '', description: 'When USE_WORKSPACE_TEUTHOLOGY false.')
27+
string(name: 'TEUTHOLOGY_VIRTUALENV_PATH', defaultValue: '', description: 'When USE_WORKSPACE_TEUTHOLOGY false.')
28+
string(name: 'SUITE_REPO', defaultValue: 'https://github.com/ceph/ceph.git', description: 'teuthology-suite --suite-repo.')
29+
string(name: 'SUITE_MACHINE_TYPE', defaultValue: 'smithi', description: 'teuthology-suite --machine-type.')
30+
string(name: 'TEUTH_CONFIG_OVERRIDE_YAML', defaultValue: '', description: 'Optional teuthology-suite argv.')
31+
string(name: 'SHAMAN_WAIT_TIMEOUT', defaultValue: '7200', description: 'Shaman wait timeout (seconds).')
32+
string(name: 'SHAMAN_WAIT_INTERVAL', defaultValue: '120', description: 'Shaman poll interval.')
33+
string(name: 'SHAMAN_WAIT_PLATFORMS', defaultValue: 'rocky-10-default,ubuntu-jammy-default,centos-9-default', description: 'Shaman platforms.')
34+
booleanParam(name: 'SKIP_SHAMAN_WAIT', defaultValue: false, description: 'Skip Shaman wait.')
35+
booleanParam(name: 'WAIT_FOR_RUNS', defaultValue: false, description: 'teuthology-wait in runner.')
36+
string(name: 'SUITE_WAIT_SLEEP', defaultValue: '15', description: 'Sleep before teuthology-wait.')
37+
booleanParam(name: 'RUN_AGGREGATE', defaultValue: false, description: 'Aggregate in runner.')
38+
string(name: 'PADDLES_URL', defaultValue: '', description: 'Optional Paddles override.')
39+
string(name: 'PULPITO_BASE', defaultValue: 'https://pulpito.ceph.com', description: 'Pulpito base URL for teuthology-runner.')
40+
}
41+
stages {
42+
stage('teuthology-runner') {
43+
steps {
44+
script {
45+
def runnerJob = (params.TEUTHOLOGY_RUNNER_JOB_NAME ?: 'teuthology-runner').trim()
46+
if (!runnerJob) {
47+
runnerJob = 'teuthology-runner'
48+
}
49+
def resolvedSha = params.CEPH_SHA1?.trim()
50+
if (!resolvedSha) {
51+
def branch = params.CEPH_BRANCH?.trim() ?: ''
52+
if (!(branch ==~ '^[a-zA-Z0-9/._-]+$')) {
53+
error("CEPH_BRANCH contains unsupported characters: ${branch}")
54+
}
55+
def timeout = params.SHAMAN_WAIT_TIMEOUT?.trim() ?: '7200'
56+
def interval = params.SHAMAN_WAIT_INTERVAL?.trim() ?: '120'
57+
if (!(timeout ==~ '^[0-9]+$') || timeout == '0') {
58+
error("SHAMAN_WAIT_TIMEOUT must be a positive integer: ${timeout}")
59+
}
60+
if (!(interval ==~ '^[0-9]+$') || interval == '0') {
61+
error("SHAMAN_WAIT_INTERVAL must be a positive integer: ${interval}")
62+
}
63+
def platforms = params.SHAMAN_WAIT_PLATFORMS?.trim() ?: 'rocky-10-default,ubuntu-jammy-default,centos-9-default'
64+
def shamanScript = "${env.WORKSPACE}/teuthology-runner/scripts/wait_for_shaman_sha1.py"
65+
if (!fileExists(shamanScript)) {
66+
error("Missing ${shamanScript}")
67+
}
68+
resolvedSha = sh(
69+
script: "python3 \"${shamanScript}\" --branch \"${branch}\" --timeout \"${timeout}\" --interval \"${interval}\" --platform \"${platforms}\" --use-available-sha",
70+
returnStdout: true,
71+
).trim()
72+
echo "Resolved CEPH_SHA1 from Shaman for branch=${branch} platforms=${platforms}: ${resolvedSha}"
73+
} else {
74+
echo "Using explicit CEPH_SHA1 from parameters: ${resolvedSha}"
75+
}
76+
if (!resolvedSha || !(resolvedSha ==~ /^[a-fA-F0-9]{7,40}$/)) {
77+
error("Resolved CEPH_SHA1 is invalid: ${resolvedSha}")
78+
}
79+
def rows = expandCadenceToRows(params.CADENCE, params.CEPH_BRANCH)
80+
if (!rows) {
81+
error("No suites for CADENCE=${params.CADENCE} CEPH_BRANCH=${params.CEPH_BRANCH}")
82+
}
83+
def json = suiteRunsRowsToJson(rows)
84+
def tp = []
85+
tp << string(name: 'AGENT_LABEL', value: params.AGENT_LABEL.trim())
86+
tp << string(name: 'CEPH_BUILD_BRANCH', value: params.CEPH_BUILD_BRANCH.trim())
87+
tp << string(name: 'CEPH_BRANCH', value: params.CEPH_BRANCH)
88+
tp << string(name: 'CEPH_REPO', value: params.CEPH_REPO.trim())
89+
tp << string(name: 'CEPH_SHA1', value: resolvedSha)
90+
tp << booleanParam(name: 'USE_WORKSPACE_TEUTHOLOGY', value: params.USE_WORKSPACE_TEUTHOLOGY)
91+
tp << string(name: 'TEUTHOLOGY_REPO_URL', value: params.TEUTHOLOGY_REPO_URL.trim())
92+
tp << string(name: 'TEUTHOLOGY_BRANCH', value: params.TEUTHOLOGY_BRANCH.trim())
93+
if (!params.USE_WORKSPACE_TEUTHOLOGY) {
94+
tp << string(name: 'TEUTHOLOGY_SCRIPT_DIR', value: params.TEUTHOLOGY_SCRIPT_DIR?.trim() ?: '')
95+
tp << string(name: 'TEUTHOLOGY_VIRTUALENV_PATH', value: params.TEUTHOLOGY_VIRTUALENV_PATH?.trim() ?: '')
96+
}
97+
if (params.TEUTH_CONFIG_OVERRIDE_YAML?.trim()) {
98+
tp << string(name: 'TEUTH_CONFIG_OVERRIDE_YAML', value: params.TEUTH_CONFIG_OVERRIDE_YAML.trim())
99+
}
100+
tp << string(name: 'SUITE_REPO', value: params.SUITE_REPO.trim())
101+
tp << string(name: 'SUITE_MACHINE_TYPE', value: params.SUITE_MACHINE_TYPE.trim())
102+
if (params.PADDLES_URL?.trim()) {
103+
tp << string(name: 'PADDLES_URL', value: params.PADDLES_URL.trim())
104+
}
105+
tp << string(name: 'SHAMAN_WAIT_TIMEOUT', value: params.SHAMAN_WAIT_TIMEOUT.trim())
106+
tp << string(name: 'SHAMAN_WAIT_INTERVAL', value: params.SHAMAN_WAIT_INTERVAL.trim())
107+
tp << string(name: 'SHAMAN_WAIT_PLATFORMS', value: params.SHAMAN_WAIT_PLATFORMS.trim())
108+
tp << string(name: 'PULPITO_BASE', value: (params.PULPITO_BASE ?: 'https://pulpito.ceph.com').trim())
109+
tp << booleanParam(name: 'SKIP_SHAMAN_WAIT', value: params.SKIP_SHAMAN_WAIT)
110+
tp << booleanParam(name: 'WAIT_FOR_RUNS', value: params.WAIT_FOR_RUNS)
111+
tp << string(name: 'SUITE_WAIT_SLEEP', value: params.SUITE_WAIT_SLEEP.trim())
112+
tp << booleanParam(name: 'RUN_AGGREGATE', value: params.RUN_AGGREGATE)
113+
tp << text(name: 'SUITE_RUNS_JSON', value: json)
114+
build job: runnerJob, wait: true, propagate: true, parameters: tp
115+
}
116+
}
117+
}
118+
}
119+
}
120+
121+
List expandCadenceToRows(String cadence, String branch) {
122+
def steps = cadenceSteps(cadence, branch.toLowerCase())
123+
if (!steps) {
124+
return []
125+
}
126+
def utcCal = Calendar.getInstance(TimeZone.getTimeZone('UTC'))
127+
def doy = utcCal.get(Calendar.DAY_OF_YEAR)
128+
def rows = []
129+
for (def st in steps) {
130+
def part = st.partitions as int
131+
def idx = doy % part
132+
def subset = "${idx}/${part}"
133+
def lim = st.limit != null ? st.limit as String : "${part}"
134+
def thr = st.threshold != null ? st.threshold as String : "${part}"
135+
def pr = st.priority ?: ''
136+
def m = [
137+
suite: st.suite,
138+
limit: lim,
139+
threshold: thr,
140+
subset: subset,
141+
priority: pr,
142+
forcePriority: st.forcePriority ? true : false,
143+
]
144+
if (st.flavor) {
145+
m.flavor = st.flavor
146+
}
147+
if (st.kernel) {
148+
m.kernel = st.kernel
149+
}
150+
if (st.filter) {
151+
m.filter = st.filter
152+
}
153+
rows << m
154+
}
155+
return rows
156+
}
157+
158+
def cadenceSteps(String cadence, String branch) {
159+
def b = branch.toLowerCase()
160+
if (cadence?.toLowerCase() == 'daily') {
161+
return [
162+
[suite: 'teuthology/nop', partitions: 1, priority: '1', forcePriority: true],
163+
[suite: 'smoke', partitions: 1, priority: '100', forcePriority: true],
164+
]
165+
}
166+
if (cadence?.toLowerCase() != 'weekly') {
167+
return []
168+
}
169+
switch (b) {
170+
case 'main':
171+
return [
172+
[suite: 'rados', partitions: 100000, priority: '101', forcePriority: true],
173+
[suite: 'rados', partitions: 100000, priority: '101', forcePriority: true, flavor: 'debug'],
174+
[suite: 'orch', partitions: 64, priority: '950', forcePriority: false],
175+
[suite: 'rbd', partitions: 128, priority: '950', forcePriority: false],
176+
[suite: 'fs', partitions: 512, priority: '700', forcePriority: false],
177+
[suite: 'powercycle', partitions: 4, priority: '950', forcePriority: false],
178+
[suite: 'rgw', partitions: 30000, limit: '1', threshold: '1', priority: '150', forcePriority: false],
179+
[suite: 'krbd', partitions: 4, priority: '950', forcePriority: false, kernel: 'testing'],
180+
[suite: 'crimson-rados', partitions: 1, priority: '101', forcePriority: true, flavor: 'debug'],
181+
[suite: 'crimson-rados', partitions: 1, priority: '101', forcePriority: true],
182+
]
183+
case 'tentacle':
184+
return [
185+
[suite: 'rados', partitions: 100000, priority: '831', forcePriority: false],
186+
[suite: 'orch', partitions: 64, priority: '830', forcePriority: false],
187+
[suite: 'rbd', partitions: 128, priority: '830', forcePriority: false],
188+
[suite: 'fs', partitions: 512, priority: '830', forcePriority: false],
189+
[suite: 'powercycle', partitions: 4, priority: '830', forcePriority: false],
190+
[suite: 'rgw', partitions: 30000, limit: '1', threshold: '1', priority: '830', forcePriority: false],
191+
[suite: 'krbd', partitions: 4, priority: '830', forcePriority: false, kernel: 'testing'],
192+
]
193+
case 'squid':
194+
return [
195+
[suite: 'rados', partitions: 100000, priority: '921', forcePriority: false],
196+
[suite: 'orch', partitions: 64, priority: '920', forcePriority: false],
197+
[suite: 'rbd', partitions: 128, priority: '920', forcePriority: false],
198+
[suite: 'fs', partitions: 512, priority: '920', forcePriority: false],
199+
[suite: 'powercycle', partitions: 4, priority: '920', forcePriority: false],
200+
[suite: 'rgw', partitions: 30000, limit: '1', threshold: '1', priority: '920', forcePriority: false],
201+
[suite: 'krbd', partitions: 4, priority: '920', forcePriority: false, kernel: 'testing'],
202+
]
203+
case 'reef':
204+
return [
205+
[suite: 'rados', partitions: 100000, priority: '920', forcePriority: false],
206+
[suite: 'orch', partitions: 64, priority: '920', forcePriority: false],
207+
[suite: 'rbd', partitions: 128, priority: '920', forcePriority: false],
208+
[suite: 'fs', partitions: 512, priority: '920', forcePriority: false],
209+
[suite: 'powercycle', partitions: 4, priority: '920', forcePriority: false],
210+
[suite: 'rgw', partitions: 30000, limit: '1', threshold: '1', priority: '920', forcePriority: false],
211+
[suite: 'krbd', partitions: 4, priority: '920', forcePriority: false, kernel: 'testing'],
212+
]
213+
default:
214+
return []
215+
}
216+
}
217+
218+
String suiteRunsRowsToJson(List rows) {
219+
def sb = new StringBuilder()
220+
sb.append('[')
221+
def first = true
222+
for (def row in rows) {
223+
if (!first) {
224+
sb.append(',')
225+
}
226+
first = false
227+
sb.append('{')
228+
def f2 = true
229+
row.each { k, v ->
230+
if (!f2) {
231+
sb.append(',')
232+
}
233+
f2 = false
234+
sb.append('"').append(k).append('":')
235+
if (v instanceof Boolean) {
236+
sb.append(v ? 'true' : 'false')
237+
} else if (v instanceof Number) {
238+
sb.append(v.toString())
239+
} else {
240+
sb.append('"').append(jsonStringEscape(v != null ? v.toString() : '')).append('"')
241+
}
242+
}
243+
sb.append('}')
244+
}
245+
sb.append(']')
246+
return sb.toString()
247+
}
248+
249+
String jsonStringEscape(String s) {
250+
if (s == null) {
251+
return ''
252+
}
253+
return s.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r').replace('\t', '\\t')
254+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Thin trigger job that fires teuthology-nightly-cadence with wait:false.
3+
* builds: Sunday UTC → weekly cadence (Sunday 20:00 trigger matches qa/crontab/teuthology-cronjobs
4+
* weekly block); any other day → daily. Manual builds use CADENCE / CEPH_BRANCH parameters.
5+
*/
6+
import java.util.Calendar
7+
import java.util.TimeZone
8+
9+
pipeline {
10+
agent any
11+
options {
12+
buildDiscarder(logRotator(numToKeepStr: '40'))
13+
skipDefaultCheckout()
14+
timestamps()
15+
ansiColor('xterm')
16+
}
17+
parameters {
18+
choice(name: 'CADENCE', choices: ['daily', 'weekly'], description: 'Cadence tier.')
19+
choice(name: 'CEPH_BRANCH', choices: ['main', 'tentacle', 'squid', 'reef'], description: 'Ceph branch to test.')
20+
string(
21+
name: 'CEPH_BUILD_BRANCH',
22+
defaultValue: 'main',
23+
description: 'ceph-build branch for the triggered teuthology-nightly-cadence build (SCM / Jenkinsfile).',
24+
)
25+
string(
26+
name: 'TEUTHOLOGY_RUNNER_JOB_NAME',
27+
defaultValue: 'teuthology-runner',
28+
description: 'Forwarded to teuthology-nightly-cadence: Jenkins job name for the runner pipeline.',
29+
)
30+
string(
31+
name: 'TEUTHOLOGY_NIGHTLY_CADENCE_JOB',
32+
defaultValue: 'teuthology-nightly-cadence',
33+
description: 'Jenkins job name of the cadence pipeline to trigger.',
34+
)
35+
}
36+
stages {
37+
stage('Trigger teuthology-nightly-cadence') {
38+
steps {
39+
script {
40+
def timerCause = currentBuild.getBuildCauses('hudson.triggers.TimerTrigger$TimerTriggerCause')
41+
def cadence = params.CADENCE
42+
if (timerCause && !timerCause.isEmpty()) {
43+
def utcCal = Calendar.getInstance(TimeZone.getTimeZone('UTC'))
44+
def dow = utcCal.get(Calendar.DAY_OF_WEEK)
45+
// Calendar: Sunday=1 … Saturday=7 (avoid java.time / static enum fields in sandbox)
46+
cadence = (dow == 1) ? 'weekly' : 'daily'
47+
echo "Timer-triggered build: CADENCE=${cadence} (Sunday=weekly per weekly cron line; else daily)."
48+
}
49+
def cephBranch = params.CEPH_BRANCH
50+
def cadenceJob = (params.TEUTHOLOGY_NIGHTLY_CADENCE_JOB ?: 'teuthology-nightly-cadence').trim() ?: 'teuthology-nightly-cadence'
51+
echo "Triggering ${cadenceJob} CADENCE=${cadence} CEPH_BRANCH=${cephBranch}"
52+
def rj = (params.TEUTHOLOGY_RUNNER_JOB_NAME ?: 'teuthology-runner').trim() ?: 'teuthology-runner'
53+
build(
54+
wait: false,
55+
job: cadenceJob,
56+
parameters: [
57+
string(name: 'CADENCE', value: cadence),
58+
string(name: 'CEPH_BRANCH', value: cephBranch),
59+
string(name: 'CEPH_BUILD_BRANCH', value: params.CEPH_BUILD_BRANCH.trim()),
60+
string(name: 'TEUTHOLOGY_RUNNER_JOB_NAME', value: rj),
61+
],
62+
)
63+
}
64+
}
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)