jupyterlab-slurm
Version:
A JupyterLab extension to interface with the Slurm workload manager.
240 lines • 11.1 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const react_1 = __importStar(require("react"));
const react_bootstrap_1 = require("react-bootstrap");
const uuid_1 = require("uuid");
// Local
const utils_1 = require("../utils");
const DataTable_1 = __importDefault(require("./DataTable"));
const JobSubmitModal_1 = __importDefault(require("./JobSubmitModal"));
const config = __importStar(require("../slurm-config/config.json"));
class SlurmManager extends react_1.Component {
constructor(props) {
super(props);
// The column index of job ID
this.JOBID_IDX = 0;
// The column index of the username
this.USER_IDX = 3;
this.requestStatusTable = new Map();
this.state = {
alerts: [],
jobSubmitModalVisible: false,
userOnly: config['userOnly'],
processingJobs: false,
reloading: false
};
}
toggleUserOnly() {
const { userOnly } = this.state;
this.setState({ userOnly: !userOnly });
}
showJobSubmitModal() {
this.setState({ jobSubmitModalVisible: true });
}
hideJobSubmitModal() {
this.setState({ jobSubmitModalVisible: false });
}
addAlert(alert) {
const { alerts } = this.state;
this.setState({ alerts: alerts.concat([alert]) });
}
makeJobRequest(route, method, body) {
return __awaiter(this, void 0, void 0, function* () {
const beforeResponse = () => {
this.setState({ processingJobs: true });
const requestID = uuid_1.v4();
this.requestStatusTable.set(requestID, 'sent');
return [requestID];
};
const afterResponse = (response, requestID) => __awaiter(this, void 0, void 0, function* () {
if (response.status !== 200) {
this.requestStatusTable.set(requestID, 'error');
this.setState({ processingJobs: false });
throw Error(response.statusText);
}
else {
const json = yield response.json();
let alert = { message: json.responseMessage };
if (json.returncode === 0) {
alert.variant = 'success';
this.requestStatusTable.set(requestID, 'received');
}
else {
alert.variant = 'danger';
this.requestStatusTable.set(requestID, 'error');
console.log(json.errorMessage);
}
this.addAlert(alert);
// Remove request pending classes from the element;
// the element may be a table row or the entire
// extension panel
// TODO: do something to row with matching job id
// if (element) {
// element.removeClass("pending");
// }
this.setState({ processingJobs: false });
// TODO: the alert and removing of the pending class
// should probably occur after table reload completes,
// but we'll need to rework synchronization here..
// If all current jobs have finished executing,
// reload the queue (using squeue)
let allJobsFinished = true;
this.requestStatusTable.forEach((status, requestID) => {
if (status === 'sent' || status === 'error') {
allJobsFinished = false;
}
});
if (allJobsFinished) {
console.log('All jobs finished.');
}
}
});
utils_1.makeRequest({ route, method, body, beforeResponse, afterResponse });
});
}
processSelectedJobs(action, rows) {
const { route, method } = (action => {
switch (action) {
case 'kill':
return { route: 'scancel', method: 'DELETE' };
case 'hold':
return { route: 'scontrol/hold', method: 'PATCH' };
case 'release':
return { route: 'scontrol/release', method: 'PATCH' };
}
})(action);
// TODO: Change backend and do all of this in a single request
rows.map(row => {
const jobID = row[this.JOBID_IDX];
this.makeJobRequest(route, method, JSON.stringify({ jobID }));
});
}
submitJob(input, inputType) {
this.setState({ jobSubmitDisabled: true, processingJobs: true });
let { serverRoot, filebrowser } = this.props;
const fileBrowserRelativePath = filebrowser.model.path;
if (serverRoot !== '/') { // Add trailing slash, but not to '/'
serverRoot += '/';
}
const fileBrowserPath = serverRoot + fileBrowserRelativePath;
const outputDir = encodeURIComponent(fileBrowserPath);
utils_1.makeRequest({
route: 'sbatch',
method: 'POST',
query: `?inputType=${inputType}&outputDir=${outputDir}`,
body: JSON.stringify({ "input": input }),
afterResponse: (response) => __awaiter(this, void 0, void 0, function* () {
if (response.ok) {
return yield response.json();
}
return null;
})
}).then((result) => {
if (result === null) {
this.setState({
jobSubmitError: "Unknown error encountered while submitting the script. Try again later.",
jobSubmitDisabled: false,
processingJobs: false
});
}
if (result["returncode"] !== 0) {
this.setState({
jobSubmitError: result["errorMessage"] == "" ? result["responseMessage"] : result["errorMessage"],
jobSubmitDisabled: false,
processingJobs: false
});
}
else {
this.hideJobSubmitModal();
this.setState({ jobSubmitDisabled: false, processingJobs: false });
}
});
}
render() {
/** We should get rid of this. If we need a higher level item to perform actions, we can create a dispatch system */
const buttons = [{
name: 'Submit Job',
id: 'submit-job',
action: () => { this.showJobSubmitModal(); },
props: {
variant: 'primary',
},
}, {
action: 'reload',
id: 'reload',
props: {
variant: 'secondary',
},
}, {
action: 'clear-selected',
id: 'clear-selected',
props: {
variant: 'warning',
},
}, {
name: 'Kill Selected Job(s)',
id: 'kill-selected',
action: (rows) => { this.processSelectedJobs('kill', rows); },
props: {
variant: 'danger',
},
}, {
name: 'Hold Selected Job(s)',
id: 'hold-selected',
action: (rows) => { this.processSelectedJobs('hold', rows); },
props: {
variant: 'danger',
},
}, {
name: 'Release Selected Job(s)',
id: 'release-selected',
action: (rows) => { this.processSelectedJobs('release', rows); },
props: {
variant: 'danger',
},
}];
const { alerts, jobSubmitModalVisible } = this.state;
return (react_1.default.createElement(react_1.default.Fragment, null,
react_1.default.createElement(DataTable_1.default, { buttons: buttons, availableColumns: config['queueCols'], userOnly: this.state.userOnly, processing: this.state.processingJobs, reloading: this.state.reloading }),
react_1.default.createElement("div", null,
react_1.default.createElement(react_bootstrap_1.Form.Check, { type: "checkbox", id: "user-only-checkbox", label: "Show my jobs only", onChange: this.toggleUserOnly.bind(this), defaultChecked: config["userOnly"] })),
react_1.default.createElement("div", { id: "alertContainer", className: "container alert-container" }, alerts.map((alert, index) => (react_1.default.createElement(react_bootstrap_1.Alert, { variant: alert.variant, key: `alert-${index}`, dismissible: true, onClose: () => {
this.state.alerts.splice(index, 1);
this.setState({});
} }, alert.message)))),
react_1.default.createElement(JobSubmitModal_1.default, { show: jobSubmitModalVisible, error: this.state.jobSubmitError, onHide: this.hideJobSubmitModal.bind(this), submitJob: this.submitJob.bind(this), disabled: this.state.jobSubmitDisabled })));
}
}
exports.default = SlurmManager;
//# sourceMappingURL=SlurmManager.js.map