-
Notifications
You must be signed in to change notification settings - Fork 85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improvement/zenko 4941 #2178
base: development/2.6
Are you sure you want to change the base?
Improvement/zenko 4941 #2178
Changes from 10 commits
e09f92a
1799ce7
f081414
ab26a96
71b255c
d460fcb
22d65a2
e479cb4
4b8c555
6eb3ac9
fbc5e59
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
import fs from 'fs'; | ||
import * as path from 'path'; | ||
import lockFile from 'proper-lockfile'; | ||
import { KubernetesHelper, Utils } from 'cli-testing'; | ||
import Zenko from 'world/Zenko'; | ||
import { | ||
|
@@ -71,12 +74,39 @@ export function createKubeCustomObjectClient(world: Zenko): CustomObjectsApi { | |
return KubernetesHelper.customObject; | ||
} | ||
|
||
export async function createJobAndWaitForCompletion(world: Zenko, jobName: string, customMetadata?: string) { | ||
export async function createJobAndWaitForCompletion( | ||
world: Zenko, | ||
jobName: string, | ||
customMetadata?: string | ||
) { | ||
const batchClient = createKubeBatchClient(world); | ||
const watchClient = createKubeWatchClient(world); | ||
|
||
const lockFilePath = path.join('/tmp', `${jobName}.lock`); | ||
let releaseLock: (() => Promise<void>) | false = false; | ||
|
||
if (!fs.existsSync(lockFilePath)) { | ||
fs.writeFileSync(lockFilePath, ''); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if the lock file exists, it means another test is waiting for this job : in that case, should the function not simply wait (polling) until the lock is removed? (i.e. the "first" test has completed its wait, and the job has indeed completed) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem with polling like that is that two tests may detect that the file does not exist at the same time, hence the use of the lock file, that internally relies on directory creation (which is atomic) and last modified dates, to ensure the whole approach is safe. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. instead of |
||
|
||
try { | ||
releaseLock = await lockFile.lock(lockFilePath, { | ||
// Expect the jobs in the queue does not take more than 5 minutes to complete | ||
stale: 10 * 60 * 1000, | ||
// use a linear backoff strategy | ||
retries: { | ||
retries: 610, | ||
factor: 1, | ||
minTimeout: 1000, | ||
maxTimeout: 1000, | ||
}, | ||
}); | ||
world.logger.debug(`Acquired lock for job: ${jobName}`); | ||
|
||
// Read the cron job and prepare the job spec | ||
const cronJob = await batchClient.readNamespacedCronJob(jobName, 'default'); | ||
const cronJobSpec = cronJob.body.spec?.jobTemplate.spec; | ||
|
||
const job = new V1Job(); | ||
const metadata = new V1ObjectMeta(); | ||
job.apiVersion = 'batch/v1'; | ||
|
@@ -87,50 +117,57 @@ export async function createJobAndWaitForCompletion(world: Zenko, jobName: strin | |
'cronjob.kubernetes.io/instantiate': 'ctst', | ||
}; | ||
if (customMetadata) { | ||
metadata.annotations = { | ||
custom: customMetadata, | ||
}; | ||
metadata.annotations.custom = customMetadata; | ||
} | ||
job.metadata = metadata; | ||
|
||
// Create the job | ||
const response = await batchClient.createNamespacedJob('default', job); | ||
world.logger.debug('job created', { | ||
job: response.body.metadata, | ||
}); | ||
world.logger.debug('Job created', { job: response.body.metadata }); | ||
|
||
const expectedJobName = response.body.metadata?.name; | ||
|
||
// Watch for job completion | ||
await new Promise<void>((resolve, reject) => { | ||
void watchClient.watch( | ||
'/apis/batch/v1/namespaces/default/jobs', | ||
{}, | ||
(type: string, apiObj, watchObj) => { | ||
if (job.metadata?.name && expectedJobName && | ||
(watchObj.object?.metadata?.name as string)?.startsWith?.(expectedJobName)) { | ||
if ( | ||
expectedJobName && | ||
(watchObj.object?.metadata?.name as string)?.startsWith?.(expectedJobName) | ||
) { | ||
if (watchObj.object?.status?.succeeded) { | ||
world.logger.debug('job succeeded', { | ||
job: job.metadata, | ||
}); | ||
world.logger.debug('Job succeeded', { job: job.metadata }); | ||
resolve(); | ||
} else if (watchObj.object?.status?.failed) { | ||
world.logger.debug('job failed', { | ||
world.logger.debug('Job failed', { | ||
job: job.metadata, | ||
object: watchObj.object, | ||
}); | ||
reject(new Error('job failed')); | ||
reject(new Error('Job failed')); | ||
} | ||
} | ||
}, reject); | ||
}, | ||
reject | ||
); | ||
}); | ||
} catch (err: unknown) { | ||
world.logger.error('error creating job', { | ||
world.logger.error('Error creating or waiting for job completion', { | ||
jobName, | ||
err, | ||
}); | ||
throw err; | ||
} finally { | ||
// Ensure the lock is released | ||
if (releaseLock) { | ||
await releaseLock(); | ||
world.logger.debug(`Released lock for job: ${jobName}`); | ||
} | ||
} | ||
} | ||
|
||
|
||
export async function waitForZenkoToStabilize( | ||
world: Zenko, needsReconciliation = false, timeout = 15 * 60 * 1000, namespace = 'default') { | ||
// ZKOP pulls the overlay configuration from Pensieve every 5 seconds | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe better to keep failing on error, except for "retriable" errors : which can be safey ignore as this step is indeed about making multiple attempts