From a4546fa5f6f3cdf89d20823f29437626d8374edf Mon Sep 17 00:00:00 2001 From: averenin-spec Date: Thu, 30 Jul 2026 14:39:33 +0200 Subject: [PATCH] Parametrizing vapor_wrapper.groovy lock (RHELHA-1045) --- pipelines/libs/global/vars/RWLock.groovy | 25 ++++++++++++ pipelines/libs/global/vars/Semaphore.groovy | 39 +++++++++++++++++++ .../vapor/vars/getProviderProperties.groovy | 8 ++++ .../libs/vapor/vars/vapor_wrapper.groovy | 2 +- 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 pipelines/libs/global/vars/Semaphore.groovy diff --git a/pipelines/libs/global/vars/RWLock.groovy b/pipelines/libs/global/vars/RWLock.groovy index bc4ddea5..a4394ea9 100644 --- a/pipelines/libs/global/vars/RWLock.groovy +++ b/pipelines/libs/global/vars/RWLock.groovy @@ -75,6 +75,18 @@ def call(Map info, String mode) } } +def call(Map info, String lockname, String stagename) { + def lockname_info = "lockfd_${stagename}_${lockname}".replace('-', '_') + node('built-in') { + do_unlock_one(info, lockname_info) + info.remove(lockname_info) + } +} + +def call(Map info, String lockname, String mode, String stagename) { + call(info, lockname, mode,stagename, null) +} + // info - the global info[:] array of the job - we store the lock FDs in here // lockname - name of the lock to get (a file in $JENKINS_HOME/locks/) // mode - READ or WRITE @@ -110,6 +122,7 @@ def call(Map info, String lockname, String mode, String stagename, Closure thing } // This MUST run on the Jenkins host, that's where the flocks are + def busy = false node('built-in') { sh "mkdir -p ${lockdir}" lockmode |= 4 // LOCK_NB (no blocking - so the job doesn't seem to "die" according to jenkins @@ -128,6 +141,11 @@ def call(Map info, String lockname, String mode, String stagename, Closure thing if (jnaflock.CLibrary.INSTANCE.flock(lockfd, lockmode) == -1) { def e = Native.getLastError() if (e == 11) { // 11 = EAGAIN + if (thingtorun == null) { + jnaflock.CLibrary.INSTANCE.close(lockfd) + busy = true + return + } wait_time = 60 println("Waiting for lock ${lockname}") } else { @@ -145,6 +163,13 @@ def call(Map info, String lockname, String mode, String stagename, Closure thing info[lockname_info]['mode'] = mode println("RWLock: ${lockname} in stage ${stagename} locked for ${mode}") log_lock('LOCKED', mode, lockname, stagename) + if (thingtorun == null) { + busy = false + } + } + + if (thingtorun == null) { + return busy } // Run a thing inside the lock, but catch any exceptions in case it fails diff --git a/pipelines/libs/global/vars/Semaphore.groovy b/pipelines/libs/global/vars/Semaphore.groovy new file mode 100644 index 00000000..d1717262 --- /dev/null +++ b/pipelines/libs/global/vars/Semaphore.groovy @@ -0,0 +1,39 @@ + +def call(Map info, int permits, String lockname, String mode, String stagename, Closure thingtorun) { + def acquired = false + + while (!acquired) { + for (int slot = 0; slot < permits; slot++) { + String slotLock = "${lockname}_${slot}" + + println("Trying ${slotLock}") + + //Polymorph for trying to lock the specific slotLock + // one time and return the result to act upon it + def busy = RWLock(info, slotLock, mode, stagename) + + if (!busy) { + acquired = true + def e = null + try { + thingtorun() + } catch (exp) { + e = exp + } finally { + //Polymorph for unlocking one lock + RWLock(info, slotLock, stagename) + } + if (e != null) { + throw(e) + } + break + } + println("${slotLock} is busy, trying next one") + } + if (!acquired) { + println("all locks are busy, sleeping ") + sleep(60) + } + } + return 0 +} diff --git a/pipelines/libs/vapor/vars/getProviderProperties.groovy b/pipelines/libs/vapor/vars/getProviderProperties.groovy index b106deda..245546e6 100644 --- a/pipelines/libs/vapor/vars/getProviderProperties.groovy +++ b/pipelines/libs/vapor/vars/getProviderProperties.groovy @@ -17,6 +17,7 @@ def call() providers['libvirt'] = ['maxjobs_smoke': 4, 'maxjobs_all': 4, 'testlevel': 'smoke', 'vers': ['rhel8', 'rhel9', 'rhel10', 'centos10'], 'has_watchdog': true, 'has_storage': true, 'has_network': true, 'weekly': true, 'allprio': 1, 'api_rate_limit': false, + 'api_creates': 1, 'defaultiscsi': '10', 'defaultuseiscsi': 'no', 'defaultblocksize': '200', @@ -41,6 +42,7 @@ def call() providers['osp'] = ['maxjobs_smoke': 4, 'maxjobs_all': 8, 'testlevel': 'all', 'vers': ['rhel8', 'rhel9'], 'has_watchdog': true, 'has_storage': true, 'has_network': true, 'weekly': true, 'allprio': 1, 'api_rate_limit': true, + 'api_creates': 1, 'defaultiscsi': '200', 'defaultuseiscsi': 'yes', 'defaultblocksize': '10', @@ -57,6 +59,7 @@ def call() providers['azure'] = ['maxjobs_smoke': 4, 'maxjobs_all': 4, 'testlevel': 'smoke', 'vers': ['rhel8', 'rhel9', 'rhel10'], 'has_watchdog': false, 'has_storage': true, 'has_network': true, 'weekly': true, 'allprio': 1, 'api_rate_limit': true, + 'api_creates': 1, 'defaultiscsi': '', 'defaultuseiscsi': 'no', 'defaultblocksize': '1024', @@ -77,6 +80,7 @@ def call() providers['aws'] = ['maxjobs_smoke': 4, 'maxjobs_all': 4, 'testlevel': 'smoke', 'vers': ['rhel8', 'rhel9', 'rhel10'], 'has_watchdog': false, 'has_storage': true, 'has_network': true, 'weekly': true, 'allprio': 1, 'api_rate_limit': true, + 'api_creates': 1, 'defaultiscsi': '', 'defaultuseiscsi': 'no', 'defaultblocksize': '300', @@ -97,6 +101,7 @@ def call() providers['gcp'] = ['maxjobs_smoke': 4, 'maxjobs_all': 4, 'testlevel': 'smoke', 'vers': ['rhel8', 'rhel9', 'rhel10'], 'has_watchdog': false, 'has_storage': true, 'has_network': true, 'weekly': true, 'allprio': 1, 'api_rate_limit': true, + 'api_creates': 1, 'defaultiscsi': '200', 'defaultuseiscsi': 'yes', 'defaultblocksize': '', @@ -117,6 +122,7 @@ def call() providers['aliyun'] = ['maxjobs_smoke': 4, 'maxjobs_all': 4, 'testlevel': 'smoke', 'vers': ['rhel8', 'rhel9', 'rhel10'], 'has_watchdog': false, 'has_storage': true, 'has_network': true, 'weekly': true, 'allprio': 1, 'api_rate_limit': true, + 'api_creates': 1, 'defaultiscsi': '200', 'defaultuseiscsi': 'no', 'defaultblocksize': '512', @@ -137,6 +143,7 @@ def call() providers['ocpv'] = ['maxjobs_smoke': 4, 'maxjobs_all': 0, 'testlevel': 'all', 'vers': ['rhel8', 'rhel9', 'rhel10', 'centos10'], 'has_watchdog': true, 'has_storage': true, 'has_network': true, 'weekly': true, 'allprio': 0, 'api_rate_limit': false, + 'api_creates': 1, 'defaultiscsi': '10', 'defaultuseiscsi': 'no', 'defaultblocksize': '200', @@ -161,6 +168,7 @@ def call() providers['ibmvpc'] = ['maxjobs_smoke': 4, 'maxjobs_all': 4, 'testlevel': 'smoke', 'vers': ['rhel8', 'rhel9'], 'has_watchdog': false, 'has_storage': false, 'has_network': false, 'weekly': true, 'allprio': 1, 'api_rate_limit': true, + 'api_creates': 1, 'defaultiscsi': '200', 'defaultuseiscsi': 'yes', 'defaultblocksize': '10', diff --git a/pipelines/libs/vapor/vars/vapor_wrapper.groovy b/pipelines/libs/vapor/vars/vapor_wrapper.groovy index b9358fc6..b8cade3c 100644 --- a/pipelines/libs/vapor/vars/vapor_wrapper.groovy +++ b/pipelines/libs/vapor/vars/vapor_wrapper.groovy @@ -77,7 +77,7 @@ def cloud_create(Map p, Map info, Integer timeout_minutes) ${p['echorun']} vapor ${p['vaporopts']} create ${p['clusteropts']} ${p['createbaseopts']} ${p['createopts']} ${p['extraopts']} ${p['provideropts']} """ - RWLock(info, "${p['provider']}_api_create", 'WRITE', 'create_stage', { + Semaphore(info, p['api_creates'], "${p['provider']}_api_create", 'WRITE', 'create_stage', { timeout(time: timeout_minutes, unit: 'MINUTES') { if (p['api_rate_limit'] == true) { res = sh(returnStatus: true, script: create_script)