Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions api/PclusterApiHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@
AUTH_PATH = os.getenv("AUTH_PATH")
API_BASE_URL = os.getenv("API_BASE_URL")
API_VERSION = os.getenv("API_VERSION", "3.1.0")
DEFAULT_API_VERSION = '3.12.0'
API_USER_ROLE = os.getenv("API_USER_ROLE")
OIDC_PROVIDER = os.getenv("OIDC_PROVIDER")
CLIENT_ID = os.getenv("CLIENT_ID")
CLIENT_SECRET = os.getenv("CLIENT_SECRET")
SECRET_ID = os.getenv("SECRET_ID")
SITE_URL = os.getenv("SITE_URL", API_BASE_URL)
SITE_URL = os.getenv("SITE_URL")
SCOPES_LIST = os.getenv("SCOPES_LIST")
REGION = os.getenv("AWS_DEFAULT_REGION")
TOKEN_URL = os.getenv("TOKEN_URL", f"{AUTH_PATH}/oauth2/token")
Expand All @@ -62,6 +63,13 @@
if not JWKS_URL:
JWKS_URL = os.getenv("JWKS_URL",
f"https://cognito-idp.{REGION}.amazonaws.com/{USER_POOL_ID}/" ".well-known/jwks.json")
API_BASE_URL_MAPPING = {}

# for url in API_BASE_URL.split(","):
# if url:
# pair=url.split("=")
# API_BASE_URL_MAPPING[pair[0]] = pair[1]



def jwt_decode(token, audience=None, access_token=None):
Expand Down Expand Up @@ -165,7 +173,7 @@ def authenticate(groups):

if (not groups):
return abort(403)

jwt_roles = set(decoded.get(USER_ROLES_CLAIM, []))
groups_granted = groups.intersection(jwt_roles)
if len(groups_granted) == 0:
Expand All @@ -191,7 +199,7 @@ def get_scopes_list():

def get_redirect_uri():
return f"{SITE_URL}/login"

# Local Endpoints


Expand Down Expand Up @@ -233,9 +241,9 @@ def ec2_action():
def get_cluster_config_text(cluster_name, region=None):
url = f"/v3/clusters/{cluster_name}"
if region:
info_resp = sigv4_request("GET", API_BASE_URL, url, params={"region": region})
info_resp = sigv4_request("GET", API_BASE_URL_MAPPING['3.12.0'], url, params={"region": region})
else:
info_resp = sigv4_request("GET", API_BASE_URL, url)
info_resp = sigv4_request("GET", API_BASE_URL_MAPPING['3.12.0'], url)
if info_resp.status_code != 200:
abort(info_resp.status_code)

Expand Down Expand Up @@ -365,7 +373,7 @@ def sacct():
user,
f"sacct {sacct_args} --json "
+ "| jq -c .jobs[0:120]\\|\\map\\({name,user,partition,state,job_id,exit_code\\}\\)",
)
)
if type(accounting) is tuple:
return accounting
else:
Expand Down Expand Up @@ -484,7 +492,7 @@ def get_dcv_session():


def get_custom_image_config():
image_info = sigv4_request("GET", API_BASE_URL, f"/v3/images/custom/{request.args.get('image_id')}").json()
image_info = sigv4_request("GET", API_BASE_URL_MAPPING['3.12.0'], f"/v3/images/custom/{request.args.get('image_id')}").json()
configuration = requests.get(image_info["imageConfiguration"]["url"])
return configuration.text

Expand Down Expand Up @@ -596,9 +604,9 @@ def _get_identity_from_token(decoded, claims):
identity["username"] = decoded["username"]

for claim in claims:
if claim in decoded:
identity["attributes"][claim] = decoded[claim]
if claim in decoded:
identity["attributes"][claim] = decoded[claim]

return identity

def get_identity():
Expand Down Expand Up @@ -735,14 +743,18 @@ def _get_params(_request):
params.pop("path")
return params

def _get_version(v):

return DEFAULT_API_VERSION


pc = Blueprint('pc', __name__)

@pc.get('/', strict_slashes=False)
@authenticated({'admin'})
@validated(params=PCProxyArgs)
def pc_proxy_get():
response = sigv4_request(request.method, API_BASE_URL, request.args.get("path"), _get_params(request))
response = sigv4_request(request.method, API_BASE_URL_MAPPING[_get_version('3.12.0')], request.args.get("path"), _get_params(request))
return response.json(), response.status_code

@pc.route('/', methods=['POST','PUT','PATCH','DELETE'], strict_slashes=False)
Expand All @@ -756,5 +768,5 @@ def pc_proxy():
except:
pass

response = sigv4_request(request.method, API_BASE_URL, request.args.get("path"), _get_params(request), body=body)
response = sigv4_request(request.method, API_BASE_URL_MAPPING[_get_version('3.12.0')], request.args.get("path"), _get_params(request), body=body)
return response.json(), response.status_code
6 changes: 6 additions & 0 deletions frontend/locales/en/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,12 @@
"alreadyExists": "Cluster with name {{clusterName}} already exists. Choose a unique name.",
"doesntMatchRegex": "Cluster name '{{clusterName}}' doesn't match the the required format. Enter a name that starts with an alphabetical character and has up to 60 characters. If Slurm accounting is configured, the name can have up to 40 characters. Valid characters: A-Z, a-z, 0-9, and - (hyphen)"
},
"version": {
"label": "Version",
"description": "The ParallelCluster version that will be used for the cluster.",
"placeholder": "Enter your cluster version",
"cannotBeBlank": "Cluster version must not be blank."
},
"region": {
"label": "Region",
"description": "The AWS Region for the cluster.",
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/__tests__/CreateCluster.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const mockRequest = executeRequest as jest.Mock

describe('given a CreateCluster command and a cluster configuration', () => {
const clusterName = 'any-name'
const clusterVersion = 'some-version'
const clusterConfiguration = 'Imds:\n ImdsSupport: v2.0'
const mockRegion = 'some-region'
const mockSelectedRegion = 'some-region'
Expand Down Expand Up @@ -37,11 +38,12 @@ describe('given a CreateCluster command and a cluster configuration', () => {
clusterConfiguration,
mockRegion,
mockSelectedRegion,
clusterVersion,
)
expect(mockRequest).toHaveBeenCalledTimes(1)
expect(mockRequest).toHaveBeenCalledWith(
'post',
'api?path=/v3/clusters&region=some-region',
'api?path=/v3/clusters&region=some-region&version=some-version',
expectedBody,
expect.any(Object),
expect.any(Object),
Expand All @@ -55,6 +57,7 @@ describe('given a CreateCluster command and a cluster configuration', () => {
clusterConfiguration,
mockRegion,
mockSelectedRegion,
clusterVersion,
false,
mockSuccessCallback,
)
Expand All @@ -71,12 +74,13 @@ describe('given a CreateCluster command and a cluster configuration', () => {
clusterConfiguration,
mockRegion,
mockSelectedRegion,
clusterVersion,
mockDryRun,
)
expect(mockRequest).toHaveBeenCalledTimes(1)
expect(mockRequest).toHaveBeenCalledWith(
'post',
'api?path=/v3/clusters&dryrun=true&region=some-region',
'api?path=/v3/clusters&dryrun=true&region=some-region&version=some-version',
expect.any(Object),
expect.any(Object),
expect.any(Object),
Expand Down Expand Up @@ -106,6 +110,7 @@ describe('given a CreateCluster command and a cluster configuration', () => {
clusterConfiguration,
mockRegion,
mockSelectedRegion,
clusterVersion,
false,
undefined,
mockErrorCallback,
Expand All @@ -128,6 +133,7 @@ describe('given a CreateCluster command and a cluster configuration', () => {
'Imds:\n ImdsSupport: v2.0',
mockRegion,
mockSelectedRegion,
clusterVersion,
)

expect(mockRequest).toHaveBeenCalledWith(
Expand All @@ -154,6 +160,7 @@ describe('given a CreateCluster command and a cluster configuration', () => {
'Imds:\n ImdsSupport: v2.0\nTags:\n - Key: foo\n Value: bar',
mockRegion,
mockSelectedRegion,
clusterVersion,
)

expect(mockRequest).toHaveBeenCalledWith(
Expand All @@ -180,6 +187,7 @@ describe('given a CreateCluster command and a cluster configuration', () => {
"Imds:\n ImdsSupport: v2.0\nTags:\n - Key: parallelcluster-ui\n Value: 'true'",
mockRegion,
mockSelectedRegion,
clusterVersion,
)

expect(mockRequest).not.toHaveBeenCalledWith(
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,15 @@ function CreateCluster(
clusterConfig: string,
region: string,
selectedRegion: string,
version: string,
dryrun = false,
successCallback?: Callback,
errorCallback?: Callback,
) {
var url = 'api?path=/v3/clusters'
url += dryrun ? '&dryrun=true' : ''
url += region ? `&region=${region}` : ''
url += version ? `&version=${version}` : ''
var body = {
clusterName: clusterName,
clusterConfiguration: mapAndApplyTags(clusterConfig),
Expand Down Expand Up @@ -159,12 +161,14 @@ function UpdateCluster(
clusterConfig: any,
dryrun = false,
forceUpdate: any,
version: any,
successCallback?: Callback,
errorCallback?: Callback,
) {
var url = `api?path=/v3/clusters/${clusterName}`
url += dryrun ? '&dryrun=true' : ''
url += forceUpdate ? '&forceUpdate=true' : ''
url += version ? `&version=${version}` : ''
var body = {clusterConfiguration: clusterConfig}
request('put', url, body)
.then((response: any) => {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/old-pages/Clusters/Actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ export default function Actions() {
clusterStatus === ClusterStatus.CreateInProgress ||
clusterStatus === ClusterStatus.DeleteInProgress ||
clusterStatus === ClusterStatus.UpdateInProgress ||
clusterStatus === ClusterStatus.CreateFailed ||
clusterVersion !== apiVersion
clusterStatus === ClusterStatus.CreateFailed
// !apiVersion.split(",").includes(clusterVersion)
const isStartFleetDisabled = fleetStatus !== 'STOPPED'
const isStopFleetDisabled = fleetStatus !== 'RUNNING'
const isDeleteDisabled =
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/old-pages/Clusters/Details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ export default function ClusterTabs() {

return cluster ? (
<>
{cluster.version !== apiVersion ? (
<Alert>{t('cluster.editAlert')}</Alert>
) : null}
{/*{!apiVersion.split(",").includes(cluster.version) ? (*/}
{/* <Alert>{t('cluster.editAlert')}</Alert>*/}
{/*) : null}*/}
<Tabs
tabs={[
{
Expand Down
1 change: 1 addition & 0 deletions frontend/src/old-pages/Clusters/Properties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export default function ClusterProperties() {
const clusterName = useState(['app', 'clusters', 'selected'])
const clusterPath = ['clusters', 'index', clusterName]
const cluster: ClusterDescription = useState(clusterPath)
const version = useState(['app', 'clusters', 'selected'])
const headNode = useState([...clusterPath, 'headNode'])
const defaultRegion = useState(['aws', 'region'])
const region = useState(['app', 'selectedRegion']) || defaultRegion
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/old-pages/Configure/Cluster.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
} from './SlurmSettings/SlurmSettings'
import InfoLink from '../../components/InfoLink'
import {ClusterNameField} from './Cluster/ClusterNameField'
import {ClusterVersionField} from './Cluster/ClusterVersionField'
import {validateClusterNameAndSetErrors} from './Cluster/clusterName.validators'
import Loading from '../../components/Loading'
import {ImdsSupportFormField} from './Cluster/ImdsSupportFormField'
Expand Down Expand Up @@ -404,6 +405,7 @@ function Cluster() {
>
<SpaceBetween direction="vertical" size="m">
<ClusterNameField />
{/*<ClusterVersionField />*/}
<RegionSelect />
<OsFormField />
<CustomAMISettings
Expand Down
53 changes: 53 additions & 0 deletions frontend/src/old-pages/Configure/Cluster/ClusterVersionField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
// with the License. A copy of the License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
// OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
// limitations under the License.

import {FormField, Input, InputProps} from '@cloudscape-design/components'
import {NonCancelableEventHandler} from '@cloudscape-design/components/internal/events'
import {useCallback} from 'react'
import {useTranslation} from 'react-i18next'
import {setState, useState} from '../../../store'

const clusterVersionPath = ['app', 'wizard', 'version']
const clusterVersionErrorPath = [
'app',
'wizard',
'errors',
'source',
'version',
]
const editingPath = ['app', 'wizard', 'editing']

export function ClusterVersionField() {
const {t} = useTranslation()
const version = useState(clusterVersionPath) || ''
const clusterVersionError = useState(clusterVersionErrorPath)
const editing = !!useState(editingPath)

const onChange: NonCancelableEventHandler<InputProps.ChangeDetail> =
useCallback(({detail}) => {
setState(clusterVersionPath, detail.value)
}, [])

return (
<FormField
label={t('wizard.cluster.version.label')}
constraintText={t('wizard.cluster.version.description')}
errorText={clusterVersionError}
>
<Input
disabled={editing}
onChange={onChange}
value={version}
placeholder={t('wizard.cluster.version.placeholder')}
/>
</FormField>
)
}
6 changes: 6 additions & 0 deletions frontend/src/old-pages/Configure/Create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ function handleCreate(
const dryRun = false
const region = getState(['app', 'wizard', 'config', 'Region'])
const selectedRegion = getState(['app', 'selectedRegion'])
const version = getState(['app', 'wizard', 'version'])
setClusterLoadingMsg(clusterName, editing, dryRun)
setState(wizardSubmissionLoading, true)

Expand All @@ -120,6 +121,7 @@ function handleCreate(
UpdateCluster(
clusterName,
clusterConfig,
version,
dryRun,
forceUpdate,
successHandler,
Expand All @@ -131,6 +133,7 @@ function handleCreate(
clusterConfig,
region,
selectedRegion,
version,
dryRun,
successHandler,
errHandler,
Expand All @@ -145,6 +148,7 @@ function handleDryRun() {
const clusterConfig = getState(configPath) || ''
const region = getState(['app', 'wizard', 'config', 'Region'])
const selectedRegion = getState(['app', 'selectedRegion'])
const version = getState(['app', 'wizard', 'version'])
const dryRun = true
setClusterLoadingMsg(clusterName, editing, dryRun)
setState(wizardSubmissionLoading, true)
Expand All @@ -163,6 +167,7 @@ function handleDryRun() {
UpdateCluster(
clusterName,
clusterConfig,
version,
dryRun,
forceUpdate,
successHandler,
Expand All @@ -174,6 +179,7 @@ function handleDryRun() {
clusterConfig,
region,
selectedRegion,
version,
dryRun,
successHandler,
errHandler,
Expand Down
Loading
Loading