From 2950a8d1b4c123e361e552e00501991f9dc2778e Mon Sep 17 00:00:00 2001 From: yiyione Date: Sun, 5 Jul 2020 17:31:38 +0800 Subject: [PATCH] [webportal] Update webportal job api to use js sdk (#4669) * update webportal job to use js sdk * update * fix --- src/webportal/src/app/home/home/conn.js | 93 +++------ .../src/app/job-submission/utils/conn.js | 71 +------ .../app/job/job-view/fabric/JobList/index.jsx | 89 +++------ .../job/job-view/fabric/job-detail/conn.js | 188 ++++++------------ 4 files changed, 122 insertions(+), 319 deletions(-) diff --git a/src/webportal/src/app/home/home/conn.js b/src/webportal/src/app/home/home/conn.js index 7d4afb482d..ee7368b03c 100644 --- a/src/webportal/src/app/home/home/conn.js +++ b/src/webportal/src/app/home/home/conn.js @@ -1,27 +1,19 @@ -// Copyright (c) Microsoft Corporation -// All rights reserved. -// -// MIT License -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and -// to permit persons to whom the Software is furnished to do so, subject to the following conditions: -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -import querystring from 'querystring'; +import { PAIV2 } from '@microsoft/openpai-js-sdk'; import config from '../../config/webportal.config'; const username = cookies.get('user'); const token = cookies.get('token'); +const client = new PAIV2.OpenPAIClient({ + rest_server_uri: config.restServerUri, + username: username, + token: token, +}); + export class UnauthorizedError extends Error { constructor(msg) { super(msg); @@ -29,55 +21,32 @@ export class UnauthorizedError extends Error { } } -async function fetchWrapper(...args) { - const res = await fetch(...args); - const json = await res.json(); - if (res.ok) { - return json; - } else { - if (json.code === 'UnauthorizedUserError') { - throw new UnauthorizedError(json.message); +const wrapper = async func => { + try { + return await func(); + } catch (err) { + if (err.data.code === 'UnauthorizedUserError') { + throw new UnauthorizedError(err.data.message); } else { - throw new Error(json.message); + throw new Error(err.data.message); } } -} +}; export async function listJobs() { - return fetchWrapper( - `${config.restServerUri}/api/v1/jobs?${querystring.stringify({ - username, - })}`, - { - headers: { - Authorization: `Bearer ${token}`, - }, - }, - ); + return wrapper(() => client.job.listJobs(username)); } export async function listAllJobs() { - return fetchWrapper(`${config.restServerUri}/api/v1/jobs`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); + return wrapper(() => client.job.listJobs()); } export async function getUserInfo() { - return fetchWrapper(`${config.restServerUri}/api/v2/user/${username}`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); + return wrapper(() => client.user.getUser(username)); } export async function listVirtualClusters() { - return fetchWrapper(`${config.restServerUri}/api/v2/virtual-clusters`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); + return wrapper(() => client.virtualCluster.listVirtualClusters()); } export async function getAvailableGpuPerNode() { @@ -132,23 +101,7 @@ export async function getLowGpuJobInfos() { export async function stopJob(job) { const { name, username } = job; - const res = await fetch( - `${config.restServerUri}/api/v1/jobs/${username}~${name}/executionType`, - { - method: 'PUT', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ value: 'STOP' }), - }, + return wrapper(() => + client.job.updateJobExecutionType(username, name, 'STOP'), ); - const json = await res.json(); - if (!res.ok) { - if (json.code === 'UnauthorizedUserError') { - throw new UnauthorizedError(json.message); - } else { - throw new Error(json.message); - } - } } diff --git a/src/webportal/src/app/job-submission/utils/conn.js b/src/webportal/src/app/job-submission/utils/conn.js index 50dc584868..b6894a25d0 100644 --- a/src/webportal/src/app/job-submission/utils/conn.js +++ b/src/webportal/src/app/job-submission/utils/conn.js @@ -22,6 +22,8 @@ const wrapper = async func => { if (err.data.code === 'UnauthorizedUserError') { alert(err.data.message); clearToken(); + } else if (err.data.code === 'NoJobConfigError') { + throw new NotFoundError(err.data.message); } else { throw new Error(err.data.message); } @@ -35,80 +37,23 @@ export class NotFoundError extends Error { } } -async function fetchWrapper(...args) { - const res = await fetch(...args); - const json = await res.json(); - if (res.ok) { - return json; - } else { - if (json.code === 'UnauthorizedUserError') { - alert(json.message); - clearToken(); - } else { - throw new Error(json.message); - } - } -} - export async function submitJob(jobProtocol) { - return fetchWrapper(`${config.restServerUri}/api/v2/jobs`, { - body: jobProtocol, - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'text/yaml', - }, - method: 'POST', - }); + const job = yaml.safeLoad(jobProtocol); + return wrapper(() => client.job.createJob(job)); } export async function fetchJobConfig(userName, jobName) { - const url = `${config.restServerUri}/api/v2/jobs/${userName}~${jobName}/config`; - const res = await fetch(url, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - const text = await res.text(); - const json = yaml.safeLoad(text); - if (res.ok) { - return json; - } else { - if (json.code === 'NoJobConfigError') { - throw new NotFoundError(json.message); - } else { - throw new Error(json.message); - } - } + return wrapper(() => client.job.getJobConfig(userName, jobName)); } export async function listUserVirtualClusters(user) { - const userInfo = await fetchWrapper( - `${config.restServerUri}/api/v1/user/${user}`, - { - headers: { - Authorization: `Bearer ${token}`, - }, - }, - ); + const userInfo = await wrapper(() => client.user.getUser(user)); return get(userInfo, 'virtualCluster', []); } export async function fetchUserGroup(api, user, token) { - const userInfoUrl = `${api}/api/v2/user/${user}`; - - return fetch(userInfoUrl, { - headers: { - Authorization: `Bearer ${token}`, - }, - }).then(response => { - if (response.ok) { - return response.json().then(responseData => { - return responseData.grouplist; - }); - } else { - throw Error(`fetch ${userInfoUrl}: HTTP ${response.status}`); - } - }); + const userInfo = await wrapper(() => client.user.getUser(user)); + return get(userInfo, 'grouplist', []); } export async function listUserStorageConfigs(user) { diff --git a/src/webportal/src/app/job/job-view/fabric/JobList/index.jsx b/src/webportal/src/app/job/job-view/fabric/JobList/index.jsx index 9c64c9926a..a5fe6ee814 100644 --- a/src/webportal/src/app/job/job-view/fabric/JobList/index.jsx +++ b/src/webportal/src/app/job/job-view/fabric/JobList/index.jsx @@ -1,20 +1,7 @@ -// Copyright (c) Microsoft Corporation -// All rights reserved. -// -// MIT License -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and -// to permit persons to whom the Software is furnished to do so, subject to the following conditions: -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +import { PAIV2 } from '@microsoft/openpai-js-sdk'; import * as querystring from 'querystring'; import React, { @@ -106,36 +93,26 @@ export default function JobList() { userAuth.checkToken(token => { jobs.forEach(job => { const { name, username } = job; - fetch( - `${webportalConfig.restServerUri}/api/v1/jobs/${username}~${name}/executionType`, - { - method: 'PUT', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ value: 'STOP' }), - }, - ) - .then(response => { - if (response.ok) { - job.executionType = 'STOP'; - delete job._statusText; - delete job._statusIndex; - setAllJobs(allJobs.slice()); + const client = new PAIV2.OpenPAIClient({ + rest_server_uri: webportalConfig.restServerUri, + username: username, + token: token, + }); + client.job + .updateJobExecutionType(username, name, 'STOP') + .then(() => { + job.executionType = 'STOP'; + delete job._statusText; + delete job._statusIndex; + setAllJobs(allJobs.slice()); + }) + .catch(err => { + if (err.data.code === 'UnauthorizedUserError') { + alert(err.data.message); + clearToken(); } else { - return response.json().then(data => { - if (data.code === 'UnauthorizedUserError') { - alert(data.message); - clearToken(); - } else { - throw new Error(data.message); - } - }); + throw new Error(err.data.message); } - }) - .catch(reason => { - setError(reason.message); }); }); }); @@ -146,21 +123,19 @@ export default function JobList() { const refreshJobs = useCallback(function refreshJobs() { setAllJobs(null); const token = userAuth.checkToken(); - fetch(`${webportalConfig.restServerUri}/api/v1/jobs`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }) - .then(response => { - if (!response.ok) { - throw Error(response.message); - } else { - return response.json(); - } + const client = new PAIV2.OpenPAIClient({ + rest_server_uri: webportalConfig.restServerUri, + username: username, + token: token, + }); + client.job + .listJobs() + .then(data => { + return data; }) .then(setAllJobs) - .catch(reason => { - setError(reason.message); + .catch(err => { + throw Error(err.data.message); }); }, []); diff --git a/src/webportal/src/app/job/job-view/fabric/job-detail/conn.js b/src/webportal/src/app/job/job-view/fabric/job-detail/conn.js index 79b93b6bdf..505107dc66 100644 --- a/src/webportal/src/app/job/job-view/fabric/job-detail/conn.js +++ b/src/webportal/src/app/job/job-view/fabric/job-detail/conn.js @@ -1,21 +1,7 @@ -// Copyright (c) Microsoft Corporation -// All rights reserved. -// -// MIT License -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -// documentation files (the "Software"), to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and -// to permit persons to whom the Software is furnished to do so, subject to the following conditions: -// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. -import yaml from 'js-yaml'; +import { PAIV2 } from '@microsoft/openpai-js-sdk'; import { isNil, get } from 'lodash'; import { clearToken } from '../../../../user/user-logout/user-logout.component'; @@ -26,6 +12,13 @@ const params = new URLSearchParams(window.location.search); const userName = params.get('username'); const jobName = params.get('jobName'); const absoluteUrlRegExp = /^[a-z][a-z\d+.-]*:/; +const token = cookies.get('token'); + +const client = new PAIV2.OpenPAIClient({ + rest_server_uri: config.restServerUri, + username: cookies.get('user'), + token: token, +}); export class NotFoundError extends Error { constructor(msg) { @@ -34,19 +27,30 @@ export class NotFoundError extends Error { } } +const wrapper = async func => { + try { + return await func(); + } catch (err) { + if (err.data.code === 'UnauthorizedUserError') { + alert(err.data.message); + clearToken(); + } else if (err.data.code === 'NoJobConfigError') { + throw new NotFoundError(err.data.message); + } else { + throw new Error(err.data.message); + } + } +}; + export async function checkAttemptAPI() { - const healthEndpoint = `${config.restServerUri}/api/v2/jobs/${userName}~${jobName}/job-attempts/healthz`; - const token = checkToken(); - const healthRes = await fetch(healthEndpoint, { - headers: { - Authorization: `Bearer ${token}`, - }, + return wrapper(async () => { + try { + await client.jobHistory.getJobAttemptsHealthz(userName, jobName); + return true; + } catch { + return false; + } }); - if (healthRes.status !== 200) { - return false; - } else { - return true; - } } export async function fetchJobRetries() { @@ -58,103 +62,49 @@ export async function fetchJobRetries() { }; } - const listAttemptsUrl = `${config.restServerUri}/api/v1/jobs/${userName}~${jobName}/job-attempts`; - const token = checkToken(); - const listRes = await fetch(listAttemptsUrl, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - if (listRes.status === 404) { - return { - isSucceeded: false, - errorMessage: 'Could not find any attempts of this job!', - jobRetries: null, - }; - } else if (listRes.status === 200) { - const jobAttempts = await listRes.json(); + try { + const jobAttempts = await client.jobHistory.getJobAttempts( + userName, + jobName, + ); return { isSucceeded: true, errorMessage: null, jobRetries: jobAttempts.filter(attempt => !attempt.isLatest), }; - } else { - return { - isSucceeded: false, - errorMessage: 'Some errors occurred!', - jobRetries: null, - }; + } catch (err) { + if (err.status === 404) { + return { + isSucceeded: false, + errorMessage: 'Could not find any attempts of this job!', + jobRetries: null, + }; + } else { + return { + isSucceeded: false, + errorMessage: 'Some errors occurred!', + jobRetries: null, + }; + } } } export async function fetchJobInfo() { - const url = userName - ? `${config.restServerUri}/api/v1/jobs/${userName}~${jobName}` - : `${config.restServerUri}/api/v1/jobs/${jobName}`; - const token = checkToken(); - const res = await fetch(url, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - const json = await res.json(); - if (res.ok) { - return json; - } else { - throw new Error(json.message); - } + return wrapper(() => client.job.getJob(userName, jobName)); } export async function fetchRawJobConfig() { - const url = userName - ? `${config.restServerUri}/api/v1/jobs/${userName}~${jobName}/config` - : `${config.restServerUri}/api/v1/jobs/${jobName}/config`; - const token = checkToken(); - const res = await fetch(url, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - const text = await res.text(); - const json = yaml.safeLoad(text); - if (res.ok) { - return json; - } else { - if (json.code === 'NoJobConfigError') { - throw new NotFoundError(json.message); - } else { - throw new Error(json.message); - } - } + return wrapper(() => client.job.getJobConfig(userName, jobName)); } export async function fetchJobConfig() { - const url = userName - ? `${config.restServerUri}/api/v2/jobs/${userName}~${jobName}/config` - : `${config.restServerUri}/api/v1/jobs/${jobName}/config`; - const token = checkToken(); - const res = await fetch(url, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - const text = await res.text(); - const json = yaml.safeLoad(text); - if (res.ok) { - return json; - } else { - if (json.code === 'NoJobConfigError') { - throw new NotFoundError(json.message); - } else { - throw new Error(json.message); - } - } + return wrapper(() => client.job.getJobConfig(userName, jobName)); } export async function fetchSshInfo() { const url = userName - ? `${config.restServerUri}/api/v1/jobs/${userName}~${jobName}/ssh` - : `${config.restServerUri}/api/v1/jobs/${jobName}/ssh`; + ? `${config.restServerUri}/api/v2/jobs/${userName}~${jobName}/ssh` + : `${config.restServerUri}/api/v2/jobs/${jobName}/ssh`; const token = checkToken(); const res = await fetch(url, { headers: { @@ -214,29 +164,9 @@ export function getJobMetricsUrl(jobInfo) { } export async function stopJob() { - const url = userName - ? `${config.restServerUri}/api/v1/jobs/${userName}~${jobName}/executionType` - : `${config.restServerUri}/api/v1/jobs/${jobName}/executionType`; - const token = checkToken(); - const res = await fetch(url, { - method: 'PUT', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - value: 'STOP', - }), - }); - const json = await res.json(); - if (res.ok) { - return json; - } else if (res.code === 'UnauthorizedUserError') { - alert(res.message); - clearToken(); - } else { - throw new Error(json.message); - } + return wrapper(() => + client.job.updateJobExecutionType(userName, jobName, 'STOP'), + ); } export async function getContainerLog(logUrl) {