Skip to content

Commit

Permalink
milestone/13 -- bug fixes (#309)
Browse files Browse the repository at this point in the history
* Use undefined fields when adding new app args

* remove undefined fields

* Job Type

* Consistent headings for job steps

* Validate execution options with batch type jobs

* Stringify non string values in description lists, allow useDetails for systems to specify request parameters

* Refactor DescriptionListValue

* Remove console statement

* Refactor JSONDisplay

* Refactor tabs, system detail tabs with json

* Handle object keys that are Javascript Sets

* Added permission checking to file toolbar

* fix unit test

* linting

* task/TUI-311 - rework of wizard form (#312)

* WIP

* Fix next step

* Thunks for validation function

* Manual validation check

* Skip to end button

* Refactor steps to JobStep array structure

* Job submission step

* linting

* Remove unused component

* Refactor generateJobDefaults

* restore reinitialize formik

* Manually trigger handleSubmit

* Shorten default job name, limit to 64 characters (#313)

* task/TUI-314 -- filter exec systems when selecting batch jobType (#315)

* Filter selectable systems if jobType is batch

* Change effect chain

* queue validation

* Fix default logical queue bug

* Display computed defaults in dropdowns

* Enforce selection of execution system

* No system select text

* Remove option text for undefined values

* linting

* task/TUI-314 -- allow undefined values for app/system defaults (#316)

* Patch setFieldValue from Formik

* exec system utils

* Use default system and queue computation

* prettier

* undefined option values

* Allow jobType to be undefined and find defaults

* unit tests

* reduce memos

* Prettier

* Refactor exec system queue validation logic

* Fix exec system queue default bug

* Handle default system is invalid for batch jobs
  • Loading branch information
jchuahtacc authored May 24, 2022
1 parent 8060c24 commit b4f132b
Show file tree
Hide file tree
Showing 29 changed files with 1,347 additions and 838 deletions.
52 changes: 52 additions & 0 deletions src/tapis-api/utils/jobDefaults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Apps, Jobs, Systems } from '@tapis/tapis-typescript';
import { generateRequiredFileInputsFromApp } from 'tapis-api/utils/jobFileInputs';
import { generateRequiredFileInputArraysFromApp } from 'tapis-api/utils/jobFileInputArrays';
import { generateJobArgsFromSpec } from 'tapis-api/utils/jobArgs';

const generateJobDefaults = ({
app,
systems,
}: {
app?: Apps.TapisApp;
systems: Array<Systems.TapisSystem>;
}): Partial<Jobs.ReqSubmitJob> => {
if (!app) {
return {};
}

const defaultValues: Partial<Jobs.ReqSubmitJob> = {
name: `${app.id}-${app.version}`,
description: app.description,
appId: app.id,
appVersion: app.version,
archiveOnAppError: app.jobAttributes?.archiveOnAppError ?? true,
archiveSystemId: app.jobAttributes?.archiveSystemId,
archiveSystemDir: app.jobAttributes?.archiveSystemDir,
nodeCount: app.jobAttributes?.nodeCount,
coresPerNode: app.jobAttributes?.coresPerNode,
jobType: app.jobType,
memoryMB: app.jobAttributes?.memoryMB,
maxMinutes: app.jobAttributes?.maxMinutes,
isMpi: app.jobAttributes?.isMpi,
mpiCmd: app.jobAttributes?.mpiCmd,
cmdPrefix: app.jobAttributes?.cmdPrefix,
fileInputs: generateRequiredFileInputsFromApp(app),
fileInputArrays: generateRequiredFileInputArraysFromApp(app),
parameterSet: {
appArgs: generateJobArgsFromSpec(
app.jobAttributes?.parameterSet?.appArgs ?? []
),
containerArgs: generateJobArgsFromSpec(
app.jobAttributes?.parameterSet?.containerArgs ?? []
),
schedulerOptions: generateJobArgsFromSpec(
app.jobAttributes?.parameterSet?.schedulerOptions ?? []
),
archiveFilter: app.jobAttributes?.parameterSet?.archiveFilter,
envVariables: app.jobAttributes?.parameterSet?.envVariables,
},
};
return defaultValues;
};

export default generateJobDefaults;
118 changes: 118 additions & 0 deletions src/tapis-api/utils/jobExecSystem.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { Apps, Systems } from '@tapis/tapis-typescript';
import '@testing-library/jest-dom/extend-expect';
import { tapisApp } from 'fixtures/apps.fixtures';
import { tapisSystem } from 'fixtures/systems.fixtures';
import {
computeDefaultQueue,
computeDefaultSystem,
validateExecSystem,
ValidateExecSystemResult,
} from './jobExecSystem';

describe('Job Exec System utils', () => {
it('determines default system', () => {
expect(computeDefaultSystem(tapisApp)).toEqual({
source: 'app',
systemId: 'testuser2.execution',
});
});
it('determines the default logical queue from an app', () => {
expect(computeDefaultQueue({}, tapisApp, [tapisSystem])).toEqual({
source: 'app',
queue: 'tapisNormal',
});
});
it('determines the default logical queue from the default system specified by the app', () => {
const tapisAppNoQueue = JSON.parse(
JSON.stringify(tapisApp)
) as Apps.TapisApp;
tapisAppNoQueue.jobAttributes!.execSystemLogicalQueue = undefined;
expect(computeDefaultQueue({}, tapisAppNoQueue, [tapisSystem])).toEqual({
source: 'app system',
queue: 'tapisNormal',
});
});
it('determines the default logical queue from a system default, where the system is selected by the job', () => {
const tapisAppNoQueue = JSON.parse(
JSON.stringify(tapisApp)
) as Apps.TapisApp;
tapisAppNoQueue.jobAttributes!.execSystemLogicalQueue = undefined;
expect(
computeDefaultQueue(
{ execSystemId: 'testuser2.execution' },
tapisAppNoQueue,
[tapisSystem]
)
).toEqual({
source: 'system',
queue: 'tapisNormal',
});
});
it('determines that there is no computed queue if a system does not exist', () => {
const tapisAppNoQueue = JSON.parse(
JSON.stringify(tapisApp)
) as Apps.TapisApp;
tapisAppNoQueue.jobAttributes!.execSystemLogicalQueue = undefined;
expect(computeDefaultQueue({}, tapisAppNoQueue, [])).toEqual({
source: undefined,
queue: undefined,
});
});
it('detects a valid job request that satisfies exec system options', () => {
expect(validateExecSystem({}, tapisApp, [tapisSystem])).toBe(
ValidateExecSystemResult.Complete
);
});
it('skips queue validation if a complete job request will be a FORK job', () => {
expect(
validateExecSystem({ jobType: Apps.JobTypeEnum.Fork }, tapisApp, [
tapisSystem,
])
).toBe(ValidateExecSystemResult.Complete);
});
it('detects an invalid job request if the specified exec system is missing', () => {
expect(validateExecSystem({}, tapisApp, [])).toBe(
ValidateExecSystemResult.ErrorExecSystemNotFound
);
});
it('detects an invalid job request if an exec system is not specified', () => {
const tapisAppNoSystem = JSON.parse(
JSON.stringify(tapisApp)
) as Apps.TapisApp;
tapisAppNoSystem.jobAttributes!.execSystemId = undefined;
expect(validateExecSystem({}, tapisAppNoSystem, [])).toBe(
ValidateExecSystemResult.ErrorNoExecSystem
);
});
it('detects an invalid job request if a queue cannot be found', () => {
const tapisAppNoQueue = JSON.parse(
JSON.stringify(tapisApp)
) as Apps.TapisApp;
tapisAppNoQueue.jobAttributes!.execSystemLogicalQueue = undefined;
const tapisSystemNoDefaultQueue = JSON.parse(
JSON.stringify(tapisSystem)
) as Systems.TapisSystem;
tapisSystemNoDefaultQueue.batchDefaultLogicalQueue = undefined;
expect(
validateExecSystem({}, tapisAppNoQueue, [tapisSystemNoDefaultQueue])
).toBe(ValidateExecSystemResult.ErrorNoQueue);
});
it('detects an invalid job request if the job is a batch job but the selected system has no queues', () => {
const tapisSystemNoQueue = JSON.parse(
JSON.stringify(tapisSystem)
) as Systems.TapisSystem;
tapisSystemNoQueue.batchLogicalQueues = [];
expect(
validateExecSystem({ jobType: Apps.JobTypeEnum.Batch }, tapisApp, [
tapisSystemNoQueue,
])
).toBe(ValidateExecSystemResult.ErrorExecSystemNoQueues);
});
it('detects an invalid job request if the job specifies a queue that the exec system does not have', () => {
expect(
validateExecSystem({ execSystemLogicalQueue: 'badQueue' }, tapisApp, [
tapisSystem,
])
).toBe(ValidateExecSystemResult.ErrorQueueNotFound);
});
});
207 changes: 207 additions & 0 deletions src/tapis-api/utils/jobExecSystem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { Apps, Jobs, Systems } from '@tapis/tapis-typescript';

type DefaultSystem = {
source?: 'app';
systemId?: string;
};

/**
* Computes the default execution system ID that will be used
*
* @param app
* @returns
*/
export const computeDefaultSystem = (app: Apps.TapisApp): DefaultSystem => {
if (app.jobAttributes?.execSystemId) {
return {
source: 'app',
systemId: app.jobAttributes?.execSystemId,
};
}
return {
source: undefined,
systemId: undefined,
};
};

type DefaultQueue = {
source?: 'app' | 'system' | 'app system';
queue?: string;
};

/**
* Computes the logical queue that will be used, if the job does not
* specify one
*
* @param job
* @param app
* @param systems
* @returns
*/
export const computeDefaultQueue = (
job: Partial<Jobs.ReqSubmitJob>,
app: Apps.TapisApp,
systems: Array<Systems.TapisSystem>
): DefaultQueue => {
// If the app specifies the logical queue, use that
if (app.jobAttributes?.execSystemLogicalQueue) {
return {
source: 'app',
queue: app.jobAttributes?.execSystemLogicalQueue,
};
}

// If the job specifies a system, look for its default logical queue
if (job.execSystemId) {
const selectedSystem = systems.find(
(system) => system.id === job.execSystemId
);
if (selectedSystem?.batchDefaultLogicalQueue) {
return {
source: 'system',
queue: selectedSystem.batchDefaultLogicalQueue,
};
}
}

// If the app specifies a system, look for its default logical queue
if (app.jobAttributes?.execSystemId) {
const appSystem = systems.find(
(system) => system.id === app.jobAttributes?.execSystemId
);
if (appSystem?.batchDefaultLogicalQueue) {
return {
source: 'app system',
queue: appSystem.batchDefaultLogicalQueue,
};
}
}

// Return a result that has no computed default logical queue
return {
source: undefined,
queue: undefined,
};
};

type DefaultJobType = {
source: 'app' | 'app system' | 'system' | 'tapis';
jobType: Apps.JobTypeEnum;
};

/**
* Determines the default jobType if one is not specified in the jobType field in a job
* using the algorithm specified at:
*
* https://tapis.readthedocs.io/en/latest/technical/jobs.html#job-type
*
* @param job
* @param app
* @param systems
* @returns
*/
export const computeDefaultJobType = (
job: Partial<Jobs.ReqSubmitJob>,
app: Apps.TapisApp,
systems: Array<Systems.TapisSystem>
): DefaultJobType => {
if (app.jobType) {
return {
source: 'app',
jobType: app.jobType!,
};
}
if (job?.execSystemId) {
const selectedSystem = systems.find(
(system) => system.id === job.execSystemId
);
if (selectedSystem?.canRunBatch) {
return {
source: 'system',
jobType: Apps.JobTypeEnum.Batch,
};
}
}
if (app.jobAttributes?.execSystemId) {
const appSystem = systems.find(
(system) => system.id === app.jobAttributes?.execSystemId
);
if (appSystem?.canRunBatch) {
return {
source: 'app system',
jobType: Apps.JobTypeEnum.Batch,
};
}
}
return {
source: 'tapis',
jobType: Apps.JobTypeEnum.Fork,
};
};

export enum ValidateExecSystemResult {
Complete = 'COMPLETE',
ErrorNoExecSystem = 'ERROR_NO_EXEC_SYSTEM',
ErrorExecSystemNotFound = 'ERROR_EXEC_SYSTEM_NOT_FOUND',
ErrorExecSystemNoQueues = 'ERROR_EXEC_SYSTEM_NO_QUEUES',
ErrorNoQueue = 'ERROR_NO_QUEUE',
ErrorQueueNotFound = 'ERROR_QUEUE_NOT_FOUND',
}

export const validateExecSystem = (
job: Partial<Jobs.ReqSubmitJob>,
app: Apps.TapisApp,
systems: Array<Systems.TapisSystem>
): ValidateExecSystemResult => {
const defaultSystem = computeDefaultSystem(app);

// Check that an exec system can be computed
if (!job.execSystemId && !defaultSystem?.systemId) {
return ValidateExecSystemResult.ErrorNoExecSystem;
}

const computedSystem = systems.find(
(system) => system.id === (job.execSystemId ?? defaultSystem?.systemId)
);
if (!computedSystem) {
return ValidateExecSystemResult.ErrorExecSystemNotFound;
}

// If the job will be a FORK job, skip queue validation
const computedJobType = computeDefaultJobType(job, app, systems);
if (
job.jobType !== Apps.JobTypeEnum.Batch &&
computedJobType.jobType === Apps.JobTypeEnum.Fork
) {
return ValidateExecSystemResult.Complete;
}

// If the job will be a BATCH job, make sure that the selected execution system
// has queues
if (!computedSystem.batchLogicalQueues?.length) {
return ValidateExecSystemResult.ErrorExecSystemNoQueues;
}

const defaultQueue = computeDefaultQueue(job, app, systems);

// If the job type will be a BATCH job, ensure that a queue is specified
// If no queue exists, there must be a fallback to the app or system default
if (!job.execSystemLogicalQueue && !defaultQueue.queue) {
return ValidateExecSystemResult.ErrorNoQueue;
}

// Check to see that the logical queue selected exists on the selected system
const selectedSystem = systems.find(
(system) => system.id === (job.execSystemId ?? defaultSystem?.systemId)
);
if (
!selectedSystem?.batchLogicalQueues?.some(
(queue) =>
queue.name === (job.execSystemLogicalQueue ?? defaultQueue.queue)
)
) {
return ValidateExecSystemResult.ErrorQueueNotFound;
}

return ValidateExecSystemResult.Complete;
};
2 changes: 1 addition & 1 deletion src/tapis-api/utils/jobRequiredFields.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Jobs } from '@tapis/tapis-typescript';

export const jobRequiredFieldsComplete = (job: Partial<Jobs.ReqSubmitJob>) => {
return !!job.name && !!job.appId && !!job.appVersion && !!job.execSystemId;
return !!job.name && !!job.appId && !!job.appVersion;
};
Loading

0 comments on commit b4f132b

Please sign in to comment.